├── LICENSE.txt ├── README.markdown ├── archive ├── archive.go ├── archive_test.go ├── gzip │ └── gzip.go ├── tar │ ├── tar.go │ └── tar_test.go └── zip │ ├── zip.go │ └── zip_test.go ├── behavior_adaptive_test.go ├── behavior_adaptivelogger.go ├── behavior_asynclogger.go ├── behavior_asyncloop_test.go ├── behavior_asynclooplogger.go ├── behavior_asynctimer_test.go ├── behavior_asynctimerlogger.go ├── behavior_synclogger.go ├── behavior_synclogger_test.go ├── cfg_config.go ├── cfg_errors.go ├── cfg_logconfig.go ├── cfg_logconfig_test.go ├── cfg_parser.go ├── cfg_parser_test.go ├── common_closer.go ├── common_constraints.go ├── common_constraints_test.go ├── common_context.go ├── common_context_test.go ├── common_exception.go ├── common_exception_test.go ├── common_flusher.go ├── common_loglevel.go ├── dispatch_custom.go ├── dispatch_customdispatcher_test.go ├── dispatch_dispatcher.go ├── dispatch_filterdispatcher.go ├── dispatch_filterdispatcher_test.go ├── dispatch_splitdispatcher.go ├── dispatch_splitdispatcher_test.go ├── doc.go ├── format.go ├── format_test.go ├── internals_baseerror.go ├── internals_byteverifiers_test.go ├── internals_fsutils.go ├── internals_xmlnode.go ├── internals_xmlnode_test.go ├── io └── iotest │ ├── iotest.go │ └── iotest_test.go ├── log.go ├── logger.go ├── writers_bufferedwriter.go ├── writers_bufferedwriter_test.go ├── writers_connwriter.go ├── writers_consolewriter.go ├── writers_filewriter.go ├── writers_filewriter_test.go ├── writers_formattedwriter.go ├── writers_formattedwriter_test.go ├── writers_rollingfilewriter.go ├── writers_rollingfilewriter_test.go └── writers_smtpwriter.go /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Cloud Instruments Co., Ltd. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the Cloud Instruments Co., Ltd. nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Seelog 2 | ======= 3 | 4 | Seelog is a powerful and easy-to-learn logging framework that provides functionality for flexible dispatching, filtering, and formatting log messages. 5 | It is natively written in the [Go](http://golang.org/) programming language. 6 | 7 | [![Build Status](https://drone.io/github.com/cihub/seelog/status.png)](https://drone.io/github.com/cihub/seelog/latest) 8 | 9 | Features 10 | ------------------ 11 | 12 | * Xml configuring to be able to change logger parameters without recompilation 13 | * Changing configurations on the fly without app restart 14 | * Possibility to set different log configurations for different project files and functions 15 | * Adjustable message formatting 16 | * Simultaneous log output to multiple streams 17 | * Choosing logger priority strategy to minimize performance hit 18 | * Different output writers 19 | * Console writer 20 | * File writer 21 | * Buffered writer (Chunk writer) 22 | * Rolling log writer (Logging with rotation) 23 | * SMTP writer 24 | * Others... (See [Wiki](https://github.com/cihub/seelog/wiki)) 25 | * Log message wrappers (JSON, XML, etc.) 26 | * Global variables and functions for easy usage in standalone apps 27 | * Functions for flexible usage in libraries 28 | 29 | Quick-start 30 | ----------- 31 | 32 | ```go 33 | package main 34 | 35 | import log "github.com/cihub/seelog" 36 | 37 | func main() { 38 | defer log.Flush() 39 | log.Info("Hello from Seelog!") 40 | } 41 | ``` 42 | 43 | Installation 44 | ------------ 45 | 46 | If you don't have the Go development environment installed, visit the 47 | [Getting Started](http://golang.org/doc/install.html) document and follow the instructions. Once you're ready, execute the following command: 48 | 49 | ``` 50 | go get -u github.com/cihub/seelog 51 | ``` 52 | 53 | *IMPORTANT*: If you are not using the latest release version of Go, check out this [wiki page](https://github.com/cihub/seelog/wiki/Notes-on-'go-get') 54 | 55 | Documentation 56 | --------------- 57 | 58 | Seelog has github wiki pages, which contain detailed how-tos references: https://github.com/cihub/seelog/wiki 59 | 60 | Examples 61 | --------------- 62 | 63 | Seelog examples can be found here: [seelog-examples](https://github.com/cihub/seelog-examples) 64 | 65 | Issues 66 | --------------- 67 | 68 | Feel free to push issues that could make Seelog better: https://github.com/cihub/seelog/issues 69 | 70 | Changelog 71 | --------------- 72 | * **v2.6** : Config using code and custom formatters 73 | * Configuration using code in addition to xml (All internal receiver/dispatcher/logger types are now exported). 74 | * Custom formatters. Check [wiki](https://github.com/cihub/seelog/wiki/Custom-formatters) 75 | * Bugfixes and internal improvements. 76 | * **v2.5** : Interaction with other systems. Part 2: custom receivers 77 | * Finished custom receivers feature. Check [wiki](https://github.com/cihub/seelog/wiki/custom-receivers) 78 | * Added 'LoggerFromCustomReceiver' 79 | * Added 'LoggerFromWriterWithMinLevelAndFormat' 80 | * Added 'LoggerFromCustomReceiver' 81 | * Added 'LoggerFromParamConfigAs...' 82 | * **v2.4** : Interaction with other systems. Part 1: wrapping seelog 83 | * Added configurable caller stack skip logic 84 | * Added 'SetAdditionalStackDepth' to 'LoggerInterface' 85 | * **v2.3** : Rethinking 'rolling' receiver 86 | * Reimplemented 'rolling' receiver 87 | * Added 'Max rolls' feature for 'rolling' receiver with type='date' 88 | * Fixed 'rolling' receiver issue: renaming on Windows 89 | * **v2.2** : go1.0 compatibility point [go1.0 tag] 90 | * Fixed internal bugs 91 | * Added 'ANSI n [;k]' format identifier: %EscN 92 | * Made current release go1 compatible 93 | * **v2.1** : Some new features 94 | * Rolling receiver archiving option. 95 | * Added format identifier: %Line 96 | * Smtp: added paths to PEM files directories 97 | * Added format identifier: %FuncShort 98 | * Warn, Error and Critical methods now return an error 99 | * **v2.0** : Second major release. BREAKING CHANGES. 100 | * Support of binaries with stripped symbols 101 | * Added log strategy: adaptive 102 | * Critical message now forces Flush() 103 | * Added predefined formats: xml-debug, xml-debug-short, xml, xml-short, json-debug, json-debug-short, json, json-short, debug, debug-short, fast 104 | * Added receiver: conn (network connection writer) 105 | * BREAKING CHANGE: added Tracef, Debugf, Infof, etc. to satisfy the print/printf principle 106 | * Bug fixes 107 | * **v1.0** : Initial release. Features: 108 | * Xml config 109 | * Changing configurations on the fly without app restart 110 | * Contraints and exceptions 111 | * Formatting 112 | * Log strategies: sync, async loop, async timer 113 | * Receivers: buffered, console, file, rolling, smtp 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /archive/archive.go: -------------------------------------------------------------------------------- 1 | package archive 2 | 3 | import ( 4 | "archive/tar" 5 | "archive/zip" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "os" 10 | "time" 11 | 12 | "github.com/cihub/seelog/archive/gzip" 13 | ) 14 | 15 | // Reader is the interface for reading files from an archive. 16 | type Reader interface { 17 | NextFile() (name string, err error) 18 | io.Reader 19 | } 20 | 21 | // ReadCloser is the interface that groups Reader with the Close method. 22 | type ReadCloser interface { 23 | Reader 24 | io.Closer 25 | } 26 | 27 | // Writer is the interface for writing files to an archived format. 28 | type Writer interface { 29 | NextFile(name string, fi os.FileInfo) error 30 | io.Writer 31 | } 32 | 33 | // WriteCloser is the interface that groups Writer with the Close method. 34 | type WriteCloser interface { 35 | Writer 36 | io.Closer 37 | } 38 | 39 | type nopCloser struct{ Reader } 40 | 41 | func (nopCloser) Close() error { return nil } 42 | 43 | // NopCloser returns a ReadCloser with a no-op Close method wrapping the 44 | // provided Reader r. 45 | func NopCloser(r Reader) ReadCloser { 46 | return nopCloser{r} 47 | } 48 | 49 | // Copy copies from src to dest until either EOF is reached on src or an error 50 | // occurs. 51 | // 52 | // When the archive format of src matches that of dst, Copy streams the files 53 | // directly into dst. Otherwise, copy buffers the contents to disk to compute 54 | // headers before writing to dst. 55 | func Copy(dst Writer, src Reader) error { 56 | switch src := src.(type) { 57 | case tarReader: 58 | if dst, ok := dst.(tarWriter); ok { 59 | return copyTar(dst, src) 60 | } 61 | case zipReader: 62 | if dst, ok := dst.(zipWriter); ok { 63 | return copyZip(dst, src) 64 | } 65 | // Switch on concrete type because gzip has no special methods 66 | case *gzip.Reader: 67 | if dst, ok := dst.(*gzip.Writer); ok { 68 | _, err := io.Copy(dst, src) 69 | return err 70 | } 71 | } 72 | 73 | return copyBuffer(dst, src) 74 | } 75 | 76 | func copyBuffer(dst Writer, src Reader) (err error) { 77 | const defaultFileMode = 0666 78 | 79 | buf, err := ioutil.TempFile("", "archive_copy_buffer") 80 | if err != nil { 81 | return err 82 | } 83 | defer os.Remove(buf.Name()) // Do not care about failure removing temp 84 | defer buf.Close() // Do not care about failure closing temp 85 | for { 86 | // Handle the next file 87 | name, err := src.NextFile() 88 | switch err { 89 | case io.EOF: // Done copying 90 | return nil 91 | default: // Failed to write: bail out 92 | return err 93 | case nil: // Proceed below 94 | } 95 | 96 | // Buffer the file 97 | if _, err := io.Copy(buf, src); err != nil { 98 | return fmt.Errorf("buffer to disk: %v", err) 99 | } 100 | 101 | // Seek to the start of the file for full file copy 102 | if _, err := buf.Seek(0, os.SEEK_SET); err != nil { 103 | return err 104 | } 105 | 106 | // Set desired file permissions 107 | if err := os.Chmod(buf.Name(), defaultFileMode); err != nil { 108 | return err 109 | } 110 | fi, err := buf.Stat() 111 | if err != nil { 112 | return err 113 | } 114 | 115 | // Write the buffered file 116 | if err := dst.NextFile(name, fi); err != nil { 117 | return err 118 | } 119 | if _, err := io.Copy(dst, buf); err != nil { 120 | return fmt.Errorf("copy to dst: %v", err) 121 | } 122 | if err := buf.Truncate(0); err != nil { 123 | return err 124 | } 125 | if _, err := buf.Seek(0, os.SEEK_SET); err != nil { 126 | return err 127 | } 128 | } 129 | } 130 | 131 | type tarReader interface { 132 | Next() (*tar.Header, error) 133 | io.Reader 134 | } 135 | 136 | type tarWriter interface { 137 | WriteHeader(hdr *tar.Header) error 138 | io.Writer 139 | } 140 | 141 | type zipReader interface { 142 | Files() []*zip.File 143 | } 144 | 145 | type zipWriter interface { 146 | CreateHeader(fh *zip.FileHeader) (io.Writer, error) 147 | } 148 | 149 | func copyTar(w tarWriter, r tarReader) error { 150 | for { 151 | hdr, err := r.Next() 152 | switch err { 153 | case io.EOF: 154 | return nil 155 | default: // Handle error 156 | return err 157 | case nil: // Proceed below 158 | } 159 | 160 | info := hdr.FileInfo() 161 | // Skip directories 162 | if info.IsDir() { 163 | continue 164 | } 165 | if err := w.WriteHeader(hdr); err != nil { 166 | return err 167 | } 168 | if _, err := io.Copy(w, r); err != nil { 169 | return err 170 | } 171 | } 172 | } 173 | 174 | func copyZip(zw zipWriter, r zipReader) error { 175 | for _, f := range r.Files() { 176 | if err := copyZipFile(zw, f); err != nil { 177 | return err 178 | } 179 | } 180 | return nil 181 | } 182 | 183 | func copyZipFile(zw zipWriter, f *zip.File) error { 184 | rc, err := f.Open() 185 | if err != nil { 186 | return err 187 | } 188 | defer rc.Close() // Read-only 189 | 190 | hdr := f.FileHeader 191 | hdr.SetModTime(time.Now()) 192 | w, err := zw.CreateHeader(&hdr) 193 | if err != nil { 194 | return err 195 | } 196 | _, err = io.Copy(w, rc) 197 | return err 198 | } 199 | -------------------------------------------------------------------------------- /archive/archive_test.go: -------------------------------------------------------------------------------- 1 | package archive_test 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "testing" 8 | 9 | "github.com/cihub/seelog/archive" 10 | "github.com/cihub/seelog/archive/gzip" 11 | "github.com/cihub/seelog/archive/tar" 12 | "github.com/cihub/seelog/archive/zip" 13 | "github.com/cihub/seelog/io/iotest" 14 | ) 15 | 16 | const ( 17 | gzipType = "gzip" 18 | tarType = "tar" 19 | zipType = "zip" 20 | ) 21 | 22 | var types = []string{gzipType, tarType, zipType} 23 | 24 | type file struct { 25 | name string 26 | contents []byte 27 | } 28 | 29 | var ( 30 | oneFile = []file{ 31 | { 32 | name: "file1", 33 | contents: []byte("This is a single log."), 34 | }, 35 | } 36 | twoFiles = []file{ 37 | { 38 | name: "file1", 39 | contents: []byte("This is a log."), 40 | }, 41 | { 42 | name: "file2", 43 | contents: []byte("This is another log."), 44 | }, 45 | } 46 | ) 47 | 48 | type testCase struct { 49 | srcType, dstType string 50 | in []file 51 | } 52 | 53 | func copyTests() map[string]testCase { 54 | // types X types X files 55 | tests := make(map[string]testCase, len(types)*len(types)*2) 56 | for _, srct := range types { 57 | for _, dstt := range types { 58 | tests[fmt.Sprintf("%s to %s: one file", srct, dstt)] = testCase{ 59 | srcType: srct, 60 | dstType: dstt, 61 | in: oneFile, 62 | } 63 | // gzip does not handle more than one file 64 | if srct != gzipType && dstt != gzipType { 65 | tests[fmt.Sprintf("%s to %s: two files", srct, dstt)] = testCase{ 66 | srcType: srct, 67 | dstType: dstt, 68 | in: twoFiles, 69 | } 70 | } 71 | } 72 | } 73 | return tests 74 | } 75 | 76 | func TestCopy(t *testing.T) { 77 | srcb, dstb := new(bytes.Buffer), new(bytes.Buffer) 78 | for tname, tt := range copyTests() { 79 | // Reset buffers between tests 80 | srcb.Reset() 81 | dstb.Reset() 82 | 83 | // Last file name (needed for gzip.NewReader) 84 | var fname string 85 | 86 | // Seed the src 87 | srcw := writer(t, tname, srcb, tt.srcType) 88 | for _, f := range tt.in { 89 | srcw.NextFile(f.name, iotest.FileInfo(t, f.contents)) 90 | mustCopy(t, tname, srcw, bytes.NewReader(f.contents)) 91 | fname = f.name 92 | } 93 | mustClose(t, tname, srcw) 94 | 95 | // Perform the copy 96 | srcr := reader(t, tname, srcb, tt.srcType, fname) 97 | dstw := writer(t, tname, dstb, tt.dstType) 98 | if err := archive.Copy(dstw, srcr); err != nil { 99 | t.Fatalf("%s: %v", tname, err) 100 | } 101 | srcr.Close() // Read-only 102 | mustClose(t, tname, dstw) 103 | 104 | // Read back dst to confirm our expectations 105 | dstr := reader(t, tname, dstb, tt.dstType, fname) 106 | for _, want := range tt.in { 107 | buf := new(bytes.Buffer) 108 | name, err := dstr.NextFile() 109 | if err != nil { 110 | t.Fatalf("%s: %v", tname, err) 111 | } 112 | mustCopy(t, tname, buf, dstr) 113 | got := file{ 114 | name: name, 115 | contents: buf.Bytes(), 116 | } 117 | 118 | switch { 119 | case got.name != want.name: 120 | t.Errorf("%s: got file %q but want file %q", 121 | tname, got.name, want.name) 122 | 123 | case !bytes.Equal(got.contents, want.contents): 124 | t.Errorf("%s: mismatched contents in %q: got %q but want %q", 125 | tname, got.name, got.contents, want.contents) 126 | } 127 | } 128 | dstr.Close() 129 | } 130 | } 131 | 132 | func writer(t *testing.T, tname string, w io.Writer, atype string) archive.WriteCloser { 133 | switch atype { 134 | case gzipType: 135 | return gzip.NewWriter(w) 136 | case tarType: 137 | return tar.NewWriter(w) 138 | case zipType: 139 | return zip.NewWriter(w) 140 | } 141 | t.Fatalf("%s: unrecognized archive type: %s", tname, atype) 142 | panic("execution continued after (*testing.T).Fatalf") 143 | } 144 | 145 | func reader(t *testing.T, tname string, buf *bytes.Buffer, atype string, fname string) archive.ReadCloser { 146 | switch atype { 147 | case gzipType: 148 | gr, err := gzip.NewReader(buf, fname) 149 | if err != nil { 150 | t.Fatalf("%s: %v", tname, err) 151 | } 152 | return gr 153 | case tarType: 154 | return archive.NopCloser(tar.NewReader(buf)) 155 | case zipType: 156 | zr, err := zip.NewReader( 157 | bytes.NewReader(buf.Bytes()), 158 | int64(buf.Len())) 159 | if err != nil { 160 | t.Fatalf("%s: new zip reader: %v", tname, err) 161 | } 162 | return archive.NopCloser(zr) 163 | } 164 | t.Fatalf("%s: unrecognized archive type: %s", tname, atype) 165 | panic("execution continued after (*testing.T).Fatalf") 166 | } 167 | 168 | func mustCopy(t *testing.T, tname string, dst io.Writer, src io.Reader) { 169 | if _, err := io.Copy(dst, src); err != nil { 170 | t.Fatalf("%s: copy: %v", tname, err) 171 | } 172 | } 173 | 174 | func mustClose(t *testing.T, tname string, c io.Closer) { 175 | if err := c.Close(); err != nil { 176 | t.Fatalf("%s: close: %v", tname, err) 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /archive/gzip/gzip.go: -------------------------------------------------------------------------------- 1 | // Package gzip implements reading and writing of gzip format compressed files. 2 | // See the compress/gzip package for more details. 3 | package gzip 4 | 5 | import ( 6 | "compress/gzip" 7 | "fmt" 8 | "io" 9 | "os" 10 | ) 11 | 12 | // Reader is an io.Reader that can be read to retrieve uncompressed data from a 13 | // gzip-format compressed file. 14 | type Reader struct { 15 | gzip.Reader 16 | name string 17 | isEOF bool 18 | } 19 | 20 | // NewReader creates a new Reader reading the given reader. 21 | func NewReader(r io.Reader, name string) (*Reader, error) { 22 | gr, err := gzip.NewReader(r) 23 | if err != nil { 24 | return nil, err 25 | } 26 | return &Reader{ 27 | Reader: *gr, 28 | name: name, 29 | }, nil 30 | } 31 | 32 | // NextFile returns the file name. Calls subsequent to the first call will 33 | // return EOF. 34 | func (r *Reader) NextFile() (name string, err error) { 35 | if r.isEOF { 36 | return "", io.EOF 37 | } 38 | 39 | r.isEOF = true 40 | return r.name, nil 41 | } 42 | 43 | // Writer is an io.WriteCloser. Writes to a Writer are compressed and written to w. 44 | type Writer struct { 45 | gzip.Writer 46 | name string 47 | noMoreFiles bool 48 | } 49 | 50 | // NextFile never returns a next file, and should not be called more than once. 51 | func (w *Writer) NextFile(name string, _ os.FileInfo) error { 52 | if w.noMoreFiles { 53 | return fmt.Errorf("gzip: only accepts one file: already received %q and now %q", w.name, name) 54 | } 55 | w.noMoreFiles = true 56 | w.name = name 57 | return nil 58 | } 59 | 60 | // NewWriter returns a new Writer. Writes to the returned writer are compressed 61 | // and written to w. 62 | func NewWriter(w io.Writer) *Writer { 63 | return &Writer{Writer: *gzip.NewWriter(w)} 64 | } 65 | -------------------------------------------------------------------------------- /archive/tar/tar.go: -------------------------------------------------------------------------------- 1 | package tar 2 | 3 | import ( 4 | "archive/tar" 5 | "io" 6 | "os" 7 | ) 8 | 9 | // Reader provides sequential access to the contents of a tar archive. 10 | type Reader struct { 11 | tar.Reader 12 | } 13 | 14 | // NewReader creates a new Reader reading from r. 15 | func NewReader(r io.Reader) *Reader { 16 | return &Reader{Reader: *tar.NewReader(r)} 17 | } 18 | 19 | // NextFile advances to the next file in the tar archive. 20 | func (r *Reader) NextFile() (name string, err error) { 21 | hdr, err := r.Next() 22 | if err != nil { 23 | return "", err 24 | } 25 | return hdr.Name, nil 26 | } 27 | 28 | // Writer provides sequential writing of a tar archive in POSIX.1 format. 29 | type Writer struct { 30 | tar.Writer 31 | closers []io.Closer 32 | } 33 | 34 | // NewWriter creates a new Writer writing to w. 35 | func NewWriter(w io.Writer) *Writer { 36 | return &Writer{Writer: *tar.NewWriter(w)} 37 | } 38 | 39 | // NewWriteMultiCloser creates a new Writer writing to w that also closes all 40 | // closers in order on close. 41 | func NewWriteMultiCloser(w io.WriteCloser, closers ...io.Closer) *Writer { 42 | return &Writer{ 43 | Writer: *tar.NewWriter(w), 44 | closers: closers, 45 | } 46 | } 47 | 48 | // NextFile computes and writes a header and prepares to accept the file's 49 | // contents. 50 | func (w *Writer) NextFile(name string, fi os.FileInfo) error { 51 | if name == "" { 52 | name = fi.Name() 53 | } 54 | hdr, err := tar.FileInfoHeader(fi, name) 55 | if err != nil { 56 | return err 57 | } 58 | hdr.Name = name 59 | return w.WriteHeader(hdr) 60 | } 61 | 62 | // Close closes the tar archive and all other closers, flushing any unwritten 63 | // data to the underlying writer. 64 | func (w *Writer) Close() error { 65 | err := w.Writer.Close() 66 | for _, c := range w.closers { 67 | if cerr := c.Close(); cerr != nil && err == nil { 68 | err = cerr 69 | } 70 | } 71 | return err 72 | } 73 | -------------------------------------------------------------------------------- /archive/tar/tar_test.go: -------------------------------------------------------------------------------- 1 | package tar_test 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "io/ioutil" 7 | "os" 8 | "testing" 9 | 10 | "github.com/cihub/seelog/archive/tar" 11 | "github.com/cihub/seelog/io/iotest" 12 | ) 13 | 14 | type file struct { 15 | name string 16 | contents []byte 17 | } 18 | 19 | var tarTests = map[string]struct{ want []file }{ 20 | "one file": { 21 | want: []file{ 22 | { 23 | name: "file", 24 | contents: []byte("I am a log file"), 25 | }, 26 | }, 27 | }, 28 | "multiple files": { 29 | want: []file{ 30 | { 31 | name: "file1", 32 | contents: []byte("I am log file 1"), 33 | }, 34 | { 35 | name: "file2", 36 | contents: []byte("I am log file 2"), 37 | }, 38 | }, 39 | }, 40 | } 41 | 42 | func TestWriterAndReader(t *testing.T) { 43 | for tname, tt := range tarTests { 44 | f, cleanup := iotest.TempFile(t) 45 | defer cleanup() 46 | writeFiles(t, f, tname, tt.want) 47 | readFiles(t, f, tname, tt.want) 48 | } 49 | } 50 | 51 | // writeFiles iterates through the files we want and writes them as a tarred 52 | // file. 53 | func writeFiles(t *testing.T, f *os.File, tname string, want []file) { 54 | w := tar.NewWriter(f) 55 | defer w.Close() 56 | 57 | // Write zipped files 58 | for _, fwant := range want { 59 | fi := iotest.FileInfo(t, fwant.contents) 60 | 61 | // Write the file 62 | err := w.NextFile(fwant.name, fi) 63 | switch err { 64 | case io.EOF: 65 | break 66 | default: 67 | t.Fatalf("%s: write header for next file: %v", tname, err) 68 | case nil: // Proceed below 69 | } 70 | if _, err := io.Copy(w, bytes.NewReader(fwant.contents)); err != nil { 71 | t.Fatalf("%s: copy to writer: %v", tname, err) 72 | } 73 | } 74 | } 75 | 76 | // readFiles iterates through tarred files and ensures they are the same. 77 | func readFiles(t *testing.T, f *os.File, tname string, want []file) { 78 | r := tar.NewReader(f) 79 | 80 | for _, fwant := range want { 81 | fname, err := r.NextFile() 82 | switch err { 83 | case io.EOF: 84 | return 85 | default: 86 | t.Fatalf("%s: read header for next file: %v", tname, err) 87 | case nil: // Proceed below 88 | } 89 | 90 | if fname != fwant.name { 91 | t.Fatalf("%s: incorrect file name: got %q but want %q", tname, fname, fwant.name) 92 | continue 93 | } 94 | 95 | gotContents, err := ioutil.ReadAll(r) 96 | if err != nil { 97 | t.Fatalf("%s: read file: %v", tname, err) 98 | } 99 | 100 | if !bytes.Equal(gotContents, fwant.contents) { 101 | t.Errorf("%s: %q = %q but want %q", tname, fname, gotContents, fwant.contents) 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /archive/zip/zip.go: -------------------------------------------------------------------------------- 1 | package zip 2 | 3 | import ( 4 | "archive/zip" 5 | "io" 6 | "os" 7 | ) 8 | 9 | // Reader provides sequential access to the contents of a zip archive. 10 | type Reader struct { 11 | zip.Reader 12 | unread []*zip.File 13 | rc io.ReadCloser 14 | } 15 | 16 | // NewReader returns a new Reader reading from r, which is assumed to have the 17 | // given size in bytes. 18 | func NewReader(r io.ReaderAt, size int64) (*Reader, error) { 19 | zr, err := zip.NewReader(r, size) 20 | if err != nil { 21 | return nil, err 22 | } 23 | return &Reader{Reader: *zr}, nil 24 | } 25 | 26 | // NextFile advances to the next file in the zip archive. 27 | func (r *Reader) NextFile() (name string, err error) { 28 | // Initialize unread 29 | if r.unread == nil { 30 | r.unread = r.Files()[:] 31 | } 32 | 33 | // Close previous file 34 | if r.rc != nil { 35 | r.rc.Close() // Read-only 36 | } 37 | 38 | if len(r.unread) == 0 { 39 | return "", io.EOF 40 | } 41 | 42 | // Open and return next unread 43 | f := r.unread[0] 44 | name, r.unread = f.Name, r.unread[1:] 45 | r.rc, err = f.Open() 46 | if err != nil { 47 | return "", err 48 | } 49 | return name, nil 50 | } 51 | 52 | func (r *Reader) Read(p []byte) (n int, err error) { 53 | return r.rc.Read(p) 54 | } 55 | 56 | // Files returns the full list of files in the zip archive. 57 | func (r *Reader) Files() []*zip.File { 58 | return r.File 59 | } 60 | 61 | // Writer provides sequential writing of a zip archive.1 format. 62 | type Writer struct { 63 | zip.Writer 64 | w io.Writer 65 | } 66 | 67 | // NewWriter returns a new Writer writing to w. 68 | func NewWriter(w io.Writer) *Writer { 69 | return &Writer{Writer: *zip.NewWriter(w)} 70 | } 71 | 72 | // NextFile computes and writes a header and prepares to accept the file's 73 | // contents. 74 | func (w *Writer) NextFile(name string, fi os.FileInfo) error { 75 | if name == "" { 76 | name = fi.Name() 77 | } 78 | hdr, err := zip.FileInfoHeader(fi) 79 | if err != nil { 80 | return err 81 | } 82 | hdr.Name = name 83 | w.w, err = w.CreateHeader(hdr) 84 | return err 85 | } 86 | 87 | func (w *Writer) Write(p []byte) (n int, err error) { 88 | return w.w.Write(p) 89 | } 90 | -------------------------------------------------------------------------------- /archive/zip/zip_test.go: -------------------------------------------------------------------------------- 1 | package zip_test 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "io/ioutil" 7 | "os" 8 | "testing" 9 | 10 | "github.com/cihub/seelog/archive/zip" 11 | "github.com/cihub/seelog/io/iotest" 12 | ) 13 | 14 | var zipTests = map[string]struct{ want map[string][]byte }{ 15 | "one file": { 16 | want: map[string][]byte{ 17 | "file": []byte("I am a log file"), 18 | }, 19 | }, 20 | "multiple files": { 21 | want: map[string][]byte{ 22 | "file1": []byte("I am log file 1"), 23 | "file2": []byte("I am log file 2"), 24 | }, 25 | }, 26 | } 27 | 28 | func TestWriterAndReader(t *testing.T) { 29 | for tname, tt := range zipTests { 30 | f, cleanup := iotest.TempFile(t) 31 | defer cleanup() 32 | writeFiles(t, f, tname, tt.want) 33 | readFiles(t, f, tname, tt.want) 34 | } 35 | } 36 | 37 | // writeFiles iterates through the files we want and writes them as a zipped 38 | // file. 39 | func writeFiles(t *testing.T, f *os.File, tname string, want map[string][]byte) { 40 | w := zip.NewWriter(f) 41 | defer w.Close() 42 | 43 | // Write zipped files 44 | for fname, fbytes := range want { 45 | fi := iotest.FileInfo(t, fbytes) 46 | 47 | // Write the file 48 | err := w.NextFile(fname, fi) 49 | switch err { 50 | case io.EOF: 51 | break 52 | default: 53 | t.Fatalf("%s: write header for next file: %v", tname, err) 54 | case nil: // Proceed below 55 | } 56 | if _, err := io.Copy(w, bytes.NewReader(fbytes)); err != nil { 57 | t.Fatalf("%s: copy to writer: %v", tname, err) 58 | } 59 | } 60 | } 61 | 62 | // readFiles iterates through zipped files and ensures they are the same. 63 | func readFiles(t *testing.T, f *os.File, tname string, want map[string][]byte) { 64 | // Get zip Reader 65 | fi, err := f.Stat() 66 | if err != nil { 67 | t.Fatalf("%s: stat zipped file: %v", tname, err) 68 | } 69 | r, err := zip.NewReader(f, fi.Size()) 70 | if err != nil { 71 | t.Fatalf("%s: %v", tname, err) 72 | } 73 | 74 | for { 75 | fname, err := r.NextFile() 76 | switch err { 77 | case io.EOF: 78 | return 79 | default: 80 | t.Fatalf("%s: read header for next file: %v", tname, err) 81 | case nil: // Proceed below 82 | } 83 | 84 | wantBytes, ok := want[fname] 85 | if !ok { 86 | t.Errorf("%s: read unwanted file: %v", tname, fname) 87 | continue 88 | } 89 | 90 | gotBytes, err := ioutil.ReadAll(r) 91 | if err != nil { 92 | t.Fatalf("%s: read file: %v", tname, err) 93 | } 94 | 95 | if !bytes.Equal(gotBytes, wantBytes) { 96 | t.Errorf("%s: %q = %q but want %q", tname, fname, gotBytes, wantBytes) 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /behavior_adaptive_test.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "bufio" 29 | "bytes" 30 | "fmt" 31 | "io" 32 | "io/ioutil" 33 | "strconv" 34 | "testing" 35 | ) 36 | 37 | func countSequencedRowsInFile(filePath string) (int64, error) { 38 | bts, err := ioutil.ReadFile(filePath) 39 | if err != nil { 40 | return 0, err 41 | } 42 | 43 | bufReader := bufio.NewReader(bytes.NewBuffer(bts)) 44 | 45 | var gotCounter int64 46 | for { 47 | line, _, bufErr := bufReader.ReadLine() 48 | if bufErr != nil && bufErr != io.EOF { 49 | return 0, bufErr 50 | } 51 | 52 | lineString := string(line) 53 | if lineString == "" { 54 | break 55 | } 56 | 57 | intVal, atoiErr := strconv.ParseInt(lineString, 10, 64) 58 | if atoiErr != nil { 59 | return 0, atoiErr 60 | } 61 | 62 | if intVal != gotCounter { 63 | return 0, fmt.Errorf("wrong order: %d Expected: %d\n", intVal, gotCounter) 64 | } 65 | 66 | gotCounter++ 67 | } 68 | 69 | return gotCounter, nil 70 | } 71 | 72 | func Test_Adaptive(t *testing.T) { 73 | fileName := "beh_test_adaptive.log" 74 | count := 100 75 | 76 | Current.Close() 77 | 78 | if e := tryRemoveFile(fileName); e != nil { 79 | t.Error(e) 80 | return 81 | } 82 | defer func() { 83 | if e := tryRemoveFile(fileName); e != nil { 84 | t.Error(e) 85 | } 86 | }() 87 | 88 | testConfig := ` 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | ` 97 | 98 | logger, _ := LoggerFromConfigAsString(testConfig) 99 | 100 | err := ReplaceLogger(logger) 101 | if err != nil { 102 | t.Error(err) 103 | return 104 | } 105 | 106 | for i := 0; i < count; i++ { 107 | Trace(strconv.Itoa(i)) 108 | } 109 | 110 | Flush() 111 | 112 | gotCount, err := countSequencedRowsInFile(fileName) 113 | if err != nil { 114 | t.Error(err) 115 | return 116 | } 117 | 118 | if int64(count) != gotCount { 119 | t.Errorf("wrong count of log messages. Expected: %v, got: %v.", count, gotCount) 120 | return 121 | } 122 | 123 | Current.Close() 124 | } 125 | -------------------------------------------------------------------------------- /behavior_adaptivelogger.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "errors" 29 | "fmt" 30 | "math" 31 | "time" 32 | ) 33 | 34 | var ( 35 | adaptiveLoggerMaxInterval = time.Minute 36 | adaptiveLoggerMaxCriticalMsgCount = uint32(1000) 37 | ) 38 | 39 | // asyncAdaptiveLogger represents asynchronous adaptive logger which acts like 40 | // an async timer logger, but its interval depends on the current message count 41 | // in the queue. 42 | // 43 | // Interval = I, minInterval = m, maxInterval = M, criticalMsgCount = C, msgCount = c: 44 | // I = m + (C - Min(c, C)) / C * (M - m) 45 | type asyncAdaptiveLogger struct { 46 | asyncLogger 47 | minInterval time.Duration 48 | criticalMsgCount uint32 49 | maxInterval time.Duration 50 | } 51 | 52 | // NewAsyncLoopLogger creates a new asynchronous adaptive logger 53 | func NewAsyncAdaptiveLogger( 54 | config *logConfig, 55 | minInterval time.Duration, 56 | maxInterval time.Duration, 57 | criticalMsgCount uint32) (*asyncAdaptiveLogger, error) { 58 | 59 | if minInterval <= 0 { 60 | return nil, errors.New("async adaptive logger min interval should be > 0") 61 | } 62 | 63 | if maxInterval > adaptiveLoggerMaxInterval { 64 | return nil, fmt.Errorf("async adaptive logger max interval should be <= %s", 65 | adaptiveLoggerMaxInterval) 66 | } 67 | 68 | if criticalMsgCount <= 0 { 69 | return nil, errors.New("async adaptive logger critical msg count should be > 0") 70 | } 71 | 72 | if criticalMsgCount > adaptiveLoggerMaxCriticalMsgCount { 73 | return nil, fmt.Errorf("async adaptive logger critical msg count should be <= %s", 74 | adaptiveLoggerMaxInterval) 75 | } 76 | 77 | asnAdaptiveLogger := new(asyncAdaptiveLogger) 78 | 79 | asnAdaptiveLogger.asyncLogger = *newAsyncLogger(config) 80 | asnAdaptiveLogger.minInterval = minInterval 81 | asnAdaptiveLogger.maxInterval = maxInterval 82 | asnAdaptiveLogger.criticalMsgCount = criticalMsgCount 83 | 84 | go asnAdaptiveLogger.processQueue() 85 | 86 | return asnAdaptiveLogger, nil 87 | } 88 | 89 | func (asnAdaptiveLogger *asyncAdaptiveLogger) processItem() (closed bool, itemCount int) { 90 | asnAdaptiveLogger.queueHasElements.L.Lock() 91 | defer asnAdaptiveLogger.queueHasElements.L.Unlock() 92 | 93 | for asnAdaptiveLogger.msgQueue.Len() == 0 && !asnAdaptiveLogger.Closed() { 94 | asnAdaptiveLogger.queueHasElements.Wait() 95 | } 96 | 97 | if asnAdaptiveLogger.Closed() { 98 | return true, asnAdaptiveLogger.msgQueue.Len() 99 | } 100 | 101 | asnAdaptiveLogger.processQueueElement() 102 | return false, asnAdaptiveLogger.msgQueue.Len() - 1 103 | } 104 | 105 | // I = m + (C - Min(c, C)) / C * (M - m) => 106 | // I = m + cDiff * mDiff, 107 | // cDiff = (C - Min(c, C)) / C) 108 | // mDiff = (M - m) 109 | func (asnAdaptiveLogger *asyncAdaptiveLogger) calcAdaptiveInterval(msgCount int) time.Duration { 110 | critCountF := float64(asnAdaptiveLogger.criticalMsgCount) 111 | cDiff := (critCountF - math.Min(float64(msgCount), critCountF)) / critCountF 112 | mDiff := float64(asnAdaptiveLogger.maxInterval - asnAdaptiveLogger.minInterval) 113 | 114 | return asnAdaptiveLogger.minInterval + time.Duration(cDiff*mDiff) 115 | } 116 | 117 | func (asnAdaptiveLogger *asyncAdaptiveLogger) processQueue() { 118 | for !asnAdaptiveLogger.Closed() { 119 | closed, itemCount := asnAdaptiveLogger.processItem() 120 | 121 | if closed { 122 | break 123 | } 124 | 125 | interval := asnAdaptiveLogger.calcAdaptiveInterval(itemCount) 126 | 127 | <-time.After(interval) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /behavior_asynclogger.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "container/list" 29 | "fmt" 30 | "sync" 31 | ) 32 | 33 | // MaxQueueSize is the critical number of messages in the queue that result in an immediate flush. 34 | const ( 35 | MaxQueueSize = 10000 36 | ) 37 | 38 | type msgQueueItem struct { 39 | level LogLevel 40 | context LogContextInterface 41 | message fmt.Stringer 42 | } 43 | 44 | // asyncLogger represents common data for all asynchronous loggers 45 | type asyncLogger struct { 46 | commonLogger 47 | msgQueue *list.List 48 | queueHasElements *sync.Cond 49 | } 50 | 51 | // newAsyncLogger creates a new asynchronous logger 52 | func newAsyncLogger(config *logConfig) *asyncLogger { 53 | asnLogger := new(asyncLogger) 54 | 55 | asnLogger.msgQueue = list.New() 56 | asnLogger.queueHasElements = sync.NewCond(new(sync.Mutex)) 57 | 58 | asnLogger.commonLogger = *newCommonLogger(config, asnLogger) 59 | 60 | return asnLogger 61 | } 62 | 63 | func (asnLogger *asyncLogger) innerLog( 64 | level LogLevel, 65 | context LogContextInterface, 66 | message fmt.Stringer) { 67 | 68 | asnLogger.addMsgToQueue(level, context, message) 69 | } 70 | 71 | func (asnLogger *asyncLogger) Close() { 72 | asnLogger.m.Lock() 73 | defer asnLogger.m.Unlock() 74 | 75 | if !asnLogger.Closed() { 76 | asnLogger.flushQueue(true) 77 | asnLogger.config.RootDispatcher.Flush() 78 | 79 | if err := asnLogger.config.RootDispatcher.Close(); err != nil { 80 | reportInternalError(err) 81 | } 82 | 83 | asnLogger.closedM.Lock() 84 | asnLogger.closed = true 85 | asnLogger.closedM.Unlock() 86 | asnLogger.queueHasElements.Broadcast() 87 | } 88 | } 89 | 90 | func (asnLogger *asyncLogger) Flush() { 91 | asnLogger.m.Lock() 92 | defer asnLogger.m.Unlock() 93 | 94 | if !asnLogger.Closed() { 95 | asnLogger.flushQueue(true) 96 | asnLogger.config.RootDispatcher.Flush() 97 | } 98 | } 99 | 100 | func (asnLogger *asyncLogger) flushQueue(lockNeeded bool) { 101 | if lockNeeded { 102 | asnLogger.queueHasElements.L.Lock() 103 | defer asnLogger.queueHasElements.L.Unlock() 104 | } 105 | 106 | for asnLogger.msgQueue.Len() > 0 { 107 | asnLogger.processQueueElement() 108 | } 109 | } 110 | 111 | func (asnLogger *asyncLogger) processQueueElement() { 112 | if asnLogger.msgQueue.Len() > 0 { 113 | backElement := asnLogger.msgQueue.Front() 114 | msg, _ := backElement.Value.(msgQueueItem) 115 | asnLogger.processLogMsg(msg.level, msg.message, msg.context) 116 | asnLogger.msgQueue.Remove(backElement) 117 | } 118 | } 119 | 120 | func (asnLogger *asyncLogger) addMsgToQueue( 121 | level LogLevel, 122 | context LogContextInterface, 123 | message fmt.Stringer) { 124 | 125 | if !asnLogger.Closed() { 126 | asnLogger.queueHasElements.L.Lock() 127 | defer asnLogger.queueHasElements.L.Unlock() 128 | 129 | if asnLogger.msgQueue.Len() >= MaxQueueSize { 130 | fmt.Printf("Seelog queue overflow: more than %v messages in the queue. Flushing.\n", MaxQueueSize) 131 | asnLogger.flushQueue(false) 132 | } 133 | 134 | queueItem := msgQueueItem{level, context, message} 135 | 136 | asnLogger.msgQueue.PushBack(queueItem) 137 | asnLogger.queueHasElements.Broadcast() 138 | } else { 139 | err := fmt.Errorf("queue closed! Cannot process element: %d %#v", level, message) 140 | reportInternalError(err) 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /behavior_asyncloop_test.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "strconv" 29 | "testing" 30 | ) 31 | 32 | func Test_Asyncloop(t *testing.T) { 33 | fileName := "beh_test_asyncloop.log" 34 | count := 100 35 | 36 | Current.Close() 37 | 38 | if e := tryRemoveFile(fileName); e != nil { 39 | t.Error(e) 40 | return 41 | } 42 | defer func() { 43 | if e := tryRemoveFile(fileName); e != nil { 44 | t.Error(e) 45 | } 46 | }() 47 | 48 | testConfig := ` 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | ` 57 | 58 | logger, _ := LoggerFromConfigAsString(testConfig) 59 | err := ReplaceLogger(logger) 60 | if err != nil { 61 | t.Error(err) 62 | return 63 | } 64 | 65 | for i := 0; i < count; i++ { 66 | Trace(strconv.Itoa(i)) 67 | } 68 | 69 | Flush() 70 | 71 | gotCount, err := countSequencedRowsInFile(fileName) 72 | if err != nil { 73 | t.Error(err) 74 | return 75 | } 76 | 77 | if int64(count) != gotCount { 78 | t.Errorf("wrong count of log messages. Expected: %v, got: %v.", count, gotCount) 79 | return 80 | } 81 | 82 | Current.Close() 83 | } 84 | 85 | func Test_AsyncloopOff(t *testing.T) { 86 | fileName := "beh_test_asyncloopoff.log" 87 | count := 100 88 | 89 | Current.Close() 90 | 91 | if e := tryRemoveFile(fileName); e != nil { 92 | t.Error(e) 93 | return 94 | } 95 | 96 | testConfig := ` 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | ` 105 | 106 | logger, _ := LoggerFromConfigAsString(testConfig) 107 | err := ReplaceLogger(logger) 108 | if err != nil { 109 | t.Error(err) 110 | return 111 | } 112 | 113 | for i := 0; i < count; i++ { 114 | Trace(strconv.Itoa(i)) 115 | } 116 | 117 | Flush() 118 | 119 | ex, err := fileExists(fileName) 120 | if err != nil { 121 | t.Error(err) 122 | } 123 | if ex { 124 | t.Errorf("logger at level OFF is not expected to create log file at all.") 125 | defer func() { 126 | if e := tryRemoveFile(fileName); e != nil { 127 | t.Error(e) 128 | } 129 | }() 130 | } 131 | 132 | Current.Close() 133 | } 134 | -------------------------------------------------------------------------------- /behavior_asynclooplogger.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | // asyncLoopLogger represents asynchronous logger which processes the log queue in 28 | // a 'for' loop 29 | type asyncLoopLogger struct { 30 | asyncLogger 31 | } 32 | 33 | // NewAsyncLoopLogger creates a new asynchronous loop logger 34 | func NewAsyncLoopLogger(config *logConfig) *asyncLoopLogger { 35 | 36 | asnLoopLogger := new(asyncLoopLogger) 37 | 38 | asnLoopLogger.asyncLogger = *newAsyncLogger(config) 39 | 40 | go asnLoopLogger.processQueue() 41 | 42 | return asnLoopLogger 43 | } 44 | 45 | func (asnLoopLogger *asyncLoopLogger) processItem() (closed bool) { 46 | asnLoopLogger.queueHasElements.L.Lock() 47 | defer asnLoopLogger.queueHasElements.L.Unlock() 48 | 49 | for asnLoopLogger.msgQueue.Len() == 0 && !asnLoopLogger.Closed() { 50 | asnLoopLogger.queueHasElements.Wait() 51 | } 52 | 53 | if asnLoopLogger.Closed() { 54 | return true 55 | } 56 | 57 | asnLoopLogger.processQueueElement() 58 | return false 59 | } 60 | 61 | func (asnLoopLogger *asyncLoopLogger) processQueue() { 62 | for !asnLoopLogger.Closed() { 63 | closed := asnLoopLogger.processItem() 64 | 65 | if closed { 66 | break 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /behavior_asynctimer_test.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "strconv" 29 | "testing" 30 | ) 31 | 32 | func Test_Asynctimer(t *testing.T) { 33 | fileName := "beh_test_asynctimer.log" 34 | count := 100 35 | 36 | Current.Close() 37 | 38 | if e := tryRemoveFile(fileName); e != nil { 39 | t.Error(e) 40 | return 41 | } 42 | defer func() { 43 | if e := tryRemoveFile(fileName); e != nil { 44 | t.Error(e) 45 | } 46 | }() 47 | 48 | testConfig := ` 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | ` 57 | 58 | logger, _ := LoggerFromConfigAsString(testConfig) 59 | err := ReplaceLogger(logger) 60 | if err != nil { 61 | t.Error(err) 62 | return 63 | } 64 | 65 | for i := 0; i < count; i++ { 66 | Trace(strconv.Itoa(i)) 67 | } 68 | 69 | Flush() 70 | 71 | gotCount, err := countSequencedRowsInFile(fileName) 72 | if err != nil { 73 | t.Error(err) 74 | return 75 | } 76 | 77 | if int64(count) != gotCount { 78 | t.Errorf("wrong count of log messages. Expected: %v, got: %v.", count, gotCount) 79 | return 80 | } 81 | 82 | Current.Close() 83 | } 84 | -------------------------------------------------------------------------------- /behavior_asynctimerlogger.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "errors" 29 | "time" 30 | ) 31 | 32 | // asyncTimerLogger represents asynchronous logger which processes the log queue each 33 | // 'duration' nanoseconds 34 | type asyncTimerLogger struct { 35 | asyncLogger 36 | interval time.Duration 37 | } 38 | 39 | // NewAsyncLoopLogger creates a new asynchronous loop logger 40 | func NewAsyncTimerLogger(config *logConfig, interval time.Duration) (*asyncTimerLogger, error) { 41 | 42 | if interval <= 0 { 43 | return nil, errors.New("async logger interval should be > 0") 44 | } 45 | 46 | asnTimerLogger := new(asyncTimerLogger) 47 | 48 | asnTimerLogger.asyncLogger = *newAsyncLogger(config) 49 | asnTimerLogger.interval = interval 50 | 51 | go asnTimerLogger.processQueue() 52 | 53 | return asnTimerLogger, nil 54 | } 55 | 56 | func (asnTimerLogger *asyncTimerLogger) processItem() (closed bool) { 57 | asnTimerLogger.queueHasElements.L.Lock() 58 | defer asnTimerLogger.queueHasElements.L.Unlock() 59 | 60 | for asnTimerLogger.msgQueue.Len() == 0 && !asnTimerLogger.Closed() { 61 | asnTimerLogger.queueHasElements.Wait() 62 | } 63 | 64 | if asnTimerLogger.Closed() { 65 | return true 66 | } 67 | 68 | asnTimerLogger.processQueueElement() 69 | return false 70 | } 71 | 72 | func (asnTimerLogger *asyncTimerLogger) processQueue() { 73 | for !asnTimerLogger.Closed() { 74 | closed := asnTimerLogger.processItem() 75 | 76 | if closed { 77 | break 78 | } 79 | 80 | <-time.After(asnTimerLogger.interval) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /behavior_synclogger.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "fmt" 29 | ) 30 | 31 | // syncLogger performs logging in the same goroutine where 'Trace/Debug/...' 32 | // func was called 33 | type syncLogger struct { 34 | commonLogger 35 | } 36 | 37 | // NewSyncLogger creates a new synchronous logger 38 | func NewSyncLogger(config *logConfig) *syncLogger { 39 | syncLogger := new(syncLogger) 40 | 41 | syncLogger.commonLogger = *newCommonLogger(config, syncLogger) 42 | 43 | return syncLogger 44 | } 45 | 46 | func (syncLogger *syncLogger) innerLog( 47 | level LogLevel, 48 | context LogContextInterface, 49 | message fmt.Stringer) { 50 | 51 | syncLogger.processLogMsg(level, message, context) 52 | } 53 | 54 | func (syncLogger *syncLogger) Close() { 55 | syncLogger.m.Lock() 56 | defer syncLogger.m.Unlock() 57 | 58 | if !syncLogger.Closed() { 59 | if err := syncLogger.config.RootDispatcher.Close(); err != nil { 60 | reportInternalError(err) 61 | } 62 | syncLogger.closedM.Lock() 63 | syncLogger.closed = true 64 | syncLogger.closedM.Unlock() 65 | } 66 | } 67 | 68 | func (syncLogger *syncLogger) Flush() { 69 | syncLogger.m.Lock() 70 | defer syncLogger.m.Unlock() 71 | 72 | if !syncLogger.Closed() { 73 | syncLogger.config.RootDispatcher.Flush() 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /behavior_synclogger_test.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "strconv" 29 | "testing" 30 | ) 31 | 32 | func Test_Sync(t *testing.T) { 33 | fileName := "beh_test_sync.log" 34 | count := 100 35 | 36 | Current.Close() 37 | 38 | if e := tryRemoveFile(fileName); e != nil { 39 | t.Error(e) 40 | return 41 | } 42 | defer func() { 43 | if e := tryRemoveFile(fileName); e != nil { 44 | t.Error(e) 45 | } 46 | }() 47 | 48 | testConfig := ` 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | ` 57 | 58 | logger, _ := LoggerFromConfigAsString(testConfig) 59 | err := ReplaceLogger(logger) 60 | if err != nil { 61 | t.Error(err) 62 | return 63 | } 64 | 65 | for i := 0; i < count; i++ { 66 | Trace(strconv.Itoa(i)) 67 | } 68 | 69 | gotCount, err := countSequencedRowsInFile(fileName) 70 | if err != nil { 71 | t.Error(err) 72 | return 73 | } 74 | 75 | if int64(count) != gotCount { 76 | t.Errorf("wrong count of log messages. Expected: %v, got: %v.", count, gotCount) 77 | return 78 | } 79 | 80 | Current.Close() 81 | } 82 | -------------------------------------------------------------------------------- /cfg_config.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "bytes" 29 | "encoding/xml" 30 | "fmt" 31 | "io" 32 | "os" 33 | ) 34 | 35 | // LoggerFromConfigAsFile creates logger with config from file. File should contain valid seelog xml. 36 | func LoggerFromConfigAsFile(fileName string) (LoggerInterface, error) { 37 | file, err := os.Open(fileName) 38 | if err != nil { 39 | return nil, err 40 | } 41 | defer file.Close() 42 | 43 | conf, err := configFromReader(file) 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | return createLoggerFromFullConfig(conf) 49 | } 50 | 51 | // LoggerFromConfigAsBytes creates a logger with config from bytes stream. Bytes should contain valid seelog xml. 52 | func LoggerFromConfigAsBytes(data []byte) (LoggerInterface, error) { 53 | conf, err := configFromReader(bytes.NewBuffer(data)) 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | return createLoggerFromFullConfig(conf) 59 | } 60 | 61 | // LoggerFromConfigAsString creates a logger with config from a string. String should contain valid seelog xml. 62 | func LoggerFromConfigAsString(data string) (LoggerInterface, error) { 63 | return LoggerFromConfigAsBytes([]byte(data)) 64 | } 65 | 66 | // LoggerFromParamConfigAsFile does the same as LoggerFromConfigAsFile, but includes special parser options. 67 | // See 'CfgParseParams' comments. 68 | func LoggerFromParamConfigAsFile(fileName string, parserParams *CfgParseParams) (LoggerInterface, error) { 69 | file, err := os.Open(fileName) 70 | if err != nil { 71 | return nil, err 72 | } 73 | defer file.Close() 74 | 75 | conf, err := configFromReaderWithConfig(file, parserParams) 76 | if err != nil { 77 | return nil, err 78 | } 79 | 80 | return createLoggerFromFullConfig(conf) 81 | } 82 | 83 | // LoggerFromParamConfigAsBytes does the same as LoggerFromConfigAsBytes, but includes special parser options. 84 | // See 'CfgParseParams' comments. 85 | func LoggerFromParamConfigAsBytes(data []byte, parserParams *CfgParseParams) (LoggerInterface, error) { 86 | conf, err := configFromReaderWithConfig(bytes.NewBuffer(data), parserParams) 87 | if err != nil { 88 | return nil, err 89 | } 90 | 91 | return createLoggerFromFullConfig(conf) 92 | } 93 | 94 | // LoggerFromParamConfigAsString does the same as LoggerFromConfigAsString, but includes special parser options. 95 | // See 'CfgParseParams' comments. 96 | func LoggerFromParamConfigAsString(data string, parserParams *CfgParseParams) (LoggerInterface, error) { 97 | return LoggerFromParamConfigAsBytes([]byte(data), parserParams) 98 | } 99 | 100 | // LoggerFromWriterWithMinLevel is shortcut for LoggerFromWriterWithMinLevelAndFormat(output, minLevel, DefaultMsgFormat) 101 | func LoggerFromWriterWithMinLevel(output io.Writer, minLevel LogLevel) (LoggerInterface, error) { 102 | return LoggerFromWriterWithMinLevelAndFormat(output, minLevel, DefaultMsgFormat) 103 | } 104 | 105 | // LoggerFromWriterWithMinLevelAndFormat creates a proxy logger that uses io.Writer as the 106 | // receiver with minimal level = minLevel and with specified format. 107 | // 108 | // All messages with level more or equal to minLevel will be written to output and 109 | // formatted using the default seelog format. 110 | // 111 | // Can be called for usage with non-Seelog systems 112 | func LoggerFromWriterWithMinLevelAndFormat(output io.Writer, minLevel LogLevel, format string) (LoggerInterface, error) { 113 | constraints, err := NewMinMaxConstraints(minLevel, CriticalLvl) 114 | if err != nil { 115 | return nil, err 116 | } 117 | formatter, err := NewFormatter(format) 118 | if err != nil { 119 | return nil, err 120 | } 121 | dispatcher, err := NewSplitDispatcher(formatter, []interface{}{output}) 122 | if err != nil { 123 | return nil, err 124 | } 125 | 126 | conf, err := newFullLoggerConfig(constraints, make([]*LogLevelException, 0), dispatcher, syncloggerTypeFromString, nil, nil) 127 | if err != nil { 128 | return nil, err 129 | } 130 | 131 | return createLoggerFromFullConfig(conf) 132 | } 133 | 134 | // LoggerFromXMLDecoder creates logger with config from a XML decoder starting from a specific node. 135 | // It should contain valid seelog xml, except for root node name. 136 | func LoggerFromXMLDecoder(xmlParser *xml.Decoder, rootNode xml.Token) (LoggerInterface, error) { 137 | conf, err := configFromXMLDecoder(xmlParser, rootNode) 138 | if err != nil { 139 | return nil, err 140 | } 141 | 142 | return createLoggerFromFullConfig(conf) 143 | } 144 | 145 | // LoggerFromCustomReceiver creates a proxy logger that uses a CustomReceiver as the 146 | // receiver. 147 | // 148 | // All messages will be sent to the specified custom receiver without additional 149 | // formatting ('%Msg' format is used). 150 | // 151 | // Check CustomReceiver, RegisterReceiver for additional info. 152 | // 153 | // NOTE 1: CustomReceiver.AfterParse is only called when a receiver is instantiated 154 | // by the config parser while parsing config. So, if you are not planning to use the 155 | // same CustomReceiver for both proxying (via LoggerFromCustomReceiver call) and 156 | // loading from config, just leave AfterParse implementation empty. 157 | // 158 | // NOTE 2: Unlike RegisterReceiver, LoggerFromCustomReceiver takes an already initialized 159 | // instance that implements CustomReceiver. So, fill it with data and perform any initialization 160 | // logic before calling this func and it won't be lost. 161 | // 162 | // So: 163 | // * RegisterReceiver takes value just to get the reflect.Type from it and then 164 | // instantiate it as many times as config is reloaded. 165 | // 166 | // * LoggerFromCustomReceiver takes value and uses it without modification and 167 | // reinstantiation, directy passing it to the dispatcher tree. 168 | func LoggerFromCustomReceiver(receiver CustomReceiver) (LoggerInterface, error) { 169 | constraints, err := NewMinMaxConstraints(TraceLvl, CriticalLvl) 170 | if err != nil { 171 | return nil, err 172 | } 173 | 174 | output, err := NewCustomReceiverDispatcherByValue(msgonlyformatter, receiver, "user-proxy", CustomReceiverInitArgs{}) 175 | if err != nil { 176 | return nil, err 177 | } 178 | dispatcher, err := NewSplitDispatcher(msgonlyformatter, []interface{}{output}) 179 | if err != nil { 180 | return nil, err 181 | } 182 | 183 | conf, err := newFullLoggerConfig(constraints, make([]*LogLevelException, 0), dispatcher, syncloggerTypeFromString, nil, nil) 184 | if err != nil { 185 | return nil, err 186 | } 187 | 188 | return createLoggerFromFullConfig(conf) 189 | } 190 | 191 | func CloneLogger(logger LoggerInterface) (LoggerInterface, error) { 192 | switch logger := logger.(type) { 193 | default: 194 | return nil, fmt.Errorf("unexpected type %T", logger) 195 | case *asyncAdaptiveLogger: 196 | clone, err := NewAsyncAdaptiveLogger(logger.commonLogger.config, logger.minInterval, logger.maxInterval, logger.criticalMsgCount) 197 | if err != nil { 198 | return nil, err 199 | } 200 | return clone, nil 201 | case *asyncLoopLogger: 202 | return NewAsyncLoopLogger(logger.commonLogger.config), nil 203 | case *asyncTimerLogger: 204 | clone, err := NewAsyncTimerLogger(logger.commonLogger.config, logger.interval) 205 | if err != nil { 206 | return nil, err 207 | } 208 | return clone, nil 209 | case *syncLogger: 210 | return NewSyncLogger(logger.commonLogger.config), nil 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /cfg_errors.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "errors" 29 | ) 30 | 31 | var ( 32 | errNodeMustHaveChildren = errors.New("node must have children") 33 | errNodeCannotHaveChildren = errors.New("node cannot have children") 34 | ) 35 | 36 | type unexpectedChildElementError struct { 37 | baseError 38 | } 39 | 40 | func newUnexpectedChildElementError(msg string) *unexpectedChildElementError { 41 | custmsg := "Unexpected child element: " + msg 42 | return &unexpectedChildElementError{baseError{message: custmsg}} 43 | } 44 | 45 | type missingArgumentError struct { 46 | baseError 47 | } 48 | 49 | func newMissingArgumentError(nodeName, attrName string) *missingArgumentError { 50 | custmsg := "Output '" + nodeName + "' has no '" + attrName + "' attribute" 51 | return &missingArgumentError{baseError{message: custmsg}} 52 | } 53 | 54 | type unexpectedAttributeError struct { 55 | baseError 56 | } 57 | 58 | func newUnexpectedAttributeError(nodeName, attr string) *unexpectedAttributeError { 59 | custmsg := nodeName + " has unexpected attribute: " + attr 60 | return &unexpectedAttributeError{baseError{message: custmsg}} 61 | } 62 | -------------------------------------------------------------------------------- /cfg_logconfig.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "errors" 29 | ) 30 | 31 | type loggerTypeFromString uint8 32 | 33 | const ( 34 | syncloggerTypeFromString = iota 35 | asyncLooploggerTypeFromString 36 | asyncTimerloggerTypeFromString 37 | adaptiveLoggerTypeFromString 38 | defaultloggerTypeFromString = asyncLooploggerTypeFromString 39 | ) 40 | 41 | const ( 42 | syncloggerTypeFromStringStr = "sync" 43 | asyncloggerTypeFromStringStr = "asyncloop" 44 | asyncTimerloggerTypeFromStringStr = "asynctimer" 45 | adaptiveLoggerTypeFromStringStr = "adaptive" 46 | ) 47 | 48 | // asyncTimerLoggerData represents specific data for async timer logger 49 | type asyncTimerLoggerData struct { 50 | AsyncInterval uint32 51 | } 52 | 53 | // adaptiveLoggerData represents specific data for adaptive timer logger 54 | type adaptiveLoggerData struct { 55 | MinInterval uint32 56 | MaxInterval uint32 57 | CriticalMsgCount uint32 58 | } 59 | 60 | var loggerTypeToStringRepresentations = map[loggerTypeFromString]string{ 61 | syncloggerTypeFromString: syncloggerTypeFromStringStr, 62 | asyncLooploggerTypeFromString: asyncloggerTypeFromStringStr, 63 | asyncTimerloggerTypeFromString: asyncTimerloggerTypeFromStringStr, 64 | adaptiveLoggerTypeFromString: adaptiveLoggerTypeFromStringStr, 65 | } 66 | 67 | // getLoggerTypeFromString parses a string and returns a corresponding logger type, if successful. 68 | func getLoggerTypeFromString(logTypeString string) (level loggerTypeFromString, found bool) { 69 | for logType, logTypeStr := range loggerTypeToStringRepresentations { 70 | if logTypeStr == logTypeString { 71 | return logType, true 72 | } 73 | } 74 | 75 | return 0, false 76 | } 77 | 78 | // logConfig stores logging configuration. Contains messages dispatcher, allowed log level rules 79 | // (general constraints and exceptions) 80 | type logConfig struct { 81 | Constraints logLevelConstraints // General log level rules (>min and 36 | 37 | 38 | 39 | 40 | 41 | ` 42 | 43 | conf, err := configFromReader(strings.NewReader(testConfig)) 44 | if err != nil { 45 | t.Errorf("parse error: %s\n", err.Error()) 46 | return 47 | } 48 | 49 | context, err := currentContext(nil) 50 | if err != nil { 51 | t.Errorf("cannot get current context:" + err.Error()) 52 | return 53 | } 54 | firstContext, err := getFirstContext() 55 | if err != nil { 56 | t.Errorf("cannot get current context:" + err.Error()) 57 | return 58 | } 59 | secondContext, err := getSecondContext() 60 | if err != nil { 61 | t.Errorf("cannot get current context:" + err.Error()) 62 | return 63 | } 64 | 65 | if !conf.IsAllowed(TraceLvl, context) { 66 | t.Errorf("error: deny trace in current context") 67 | } 68 | if conf.IsAllowed(TraceLvl, firstContext) { 69 | t.Errorf("error: allow trace in first context") 70 | } 71 | if conf.IsAllowed(ErrorLvl, context) { 72 | t.Errorf("error: allow error in current context") 73 | } 74 | if !conf.IsAllowed(ErrorLvl, secondContext) { 75 | t.Errorf("error: deny error in second context") 76 | } 77 | 78 | // cache test 79 | if !conf.IsAllowed(TraceLvl, context) { 80 | t.Errorf("error: deny trace in current context") 81 | } 82 | if conf.IsAllowed(TraceLvl, firstContext) { 83 | t.Errorf("error: allow trace in first context") 84 | } 85 | if conf.IsAllowed(ErrorLvl, context) { 86 | t.Errorf("error: allow error in current context") 87 | } 88 | if !conf.IsAllowed(ErrorLvl, secondContext) { 89 | t.Errorf("error: deny error in second context") 90 | } 91 | } 92 | 93 | func getFirstContext() (LogContextInterface, error) { 94 | return currentContext(nil) 95 | } 96 | 97 | func getSecondContext() (LogContextInterface, error) { 98 | return currentContext(nil) 99 | } 100 | -------------------------------------------------------------------------------- /common_closer.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | -------------------------------------------------------------------------------- /common_constraints.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "errors" 29 | "fmt" 30 | "strings" 31 | ) 32 | 33 | // Represents constraints which form a general rule for log levels selection 34 | type logLevelConstraints interface { 35 | IsAllowed(level LogLevel) bool 36 | } 37 | 38 | // A minMaxConstraints represents constraints which use minimal and maximal allowed log levels. 39 | type minMaxConstraints struct { 40 | min LogLevel 41 | max LogLevel 42 | } 43 | 44 | // NewMinMaxConstraints creates a new minMaxConstraints struct with the specified min and max levels. 45 | func NewMinMaxConstraints(min LogLevel, max LogLevel) (*minMaxConstraints, error) { 46 | if min > max { 47 | return nil, fmt.Errorf("min level can't be greater than max. Got min: %d, max: %d", min, max) 48 | } 49 | if min < TraceLvl || min > CriticalLvl { 50 | return nil, fmt.Errorf("min level can't be less than Trace or greater than Critical. Got min: %d", min) 51 | } 52 | if max < TraceLvl || max > CriticalLvl { 53 | return nil, fmt.Errorf("max level can't be less than Trace or greater than Critical. Got max: %d", max) 54 | } 55 | 56 | return &minMaxConstraints{min, max}, nil 57 | } 58 | 59 | // IsAllowed returns true, if log level is in [min, max] range (inclusive). 60 | func (minMaxConstr *minMaxConstraints) IsAllowed(level LogLevel) bool { 61 | return level >= minMaxConstr.min && level <= minMaxConstr.max 62 | } 63 | 64 | func (minMaxConstr *minMaxConstraints) String() string { 65 | return fmt.Sprintf("Min: %s. Max: %s", minMaxConstr.min, minMaxConstr.max) 66 | } 67 | 68 | //======================================================= 69 | 70 | // A listConstraints represents constraints which use allowed log levels list. 71 | type listConstraints struct { 72 | allowedLevels map[LogLevel]bool 73 | } 74 | 75 | // NewListConstraints creates a new listConstraints struct with the specified allowed levels. 76 | func NewListConstraints(allowList []LogLevel) (*listConstraints, error) { 77 | if allowList == nil { 78 | return nil, errors.New("list can't be nil") 79 | } 80 | 81 | allowLevels, err := createMapFromList(allowList) 82 | if err != nil { 83 | return nil, err 84 | } 85 | err = validateOffLevel(allowLevels) 86 | if err != nil { 87 | return nil, err 88 | } 89 | 90 | return &listConstraints{allowLevels}, nil 91 | } 92 | 93 | func (listConstr *listConstraints) String() string { 94 | allowedList := "List: " 95 | 96 | listLevel := make([]string, len(listConstr.allowedLevels)) 97 | 98 | var logLevel LogLevel 99 | i := 0 100 | for logLevel = TraceLvl; logLevel <= Off; logLevel++ { 101 | if listConstr.allowedLevels[logLevel] { 102 | listLevel[i] = logLevel.String() 103 | i++ 104 | } 105 | } 106 | 107 | allowedList += strings.Join(listLevel, ",") 108 | 109 | return allowedList 110 | } 111 | 112 | func createMapFromList(allowedList []LogLevel) (map[LogLevel]bool, error) { 113 | allowedLevels := make(map[LogLevel]bool, 0) 114 | for _, level := range allowedList { 115 | if level < TraceLvl || level > Off { 116 | return nil, fmt.Errorf("level can't be less than Trace or greater than Critical. Got level: %d", level) 117 | } 118 | allowedLevels[level] = true 119 | } 120 | return allowedLevels, nil 121 | } 122 | func validateOffLevel(allowedLevels map[LogLevel]bool) error { 123 | if _, ok := allowedLevels[Off]; ok && len(allowedLevels) > 1 { 124 | return errors.New("logLevel Off cant be mixed with other levels") 125 | } 126 | 127 | return nil 128 | } 129 | 130 | // IsAllowed returns true, if log level is in allowed log levels list. 131 | // If the list contains the only item 'common.Off' then IsAllowed will always return false for any input values. 132 | func (listConstr *listConstraints) IsAllowed(level LogLevel) bool { 133 | for l := range listConstr.allowedLevels { 134 | if l == level && level != Off { 135 | return true 136 | } 137 | } 138 | 139 | return false 140 | } 141 | 142 | // AllowedLevels returns allowed levels configuration as a map. 143 | func (listConstr *listConstraints) AllowedLevels() map[LogLevel]bool { 144 | return listConstr.allowedLevels 145 | } 146 | 147 | //======================================================= 148 | 149 | type offConstraints struct { 150 | } 151 | 152 | func NewOffConstraints() (*offConstraints, error) { 153 | return &offConstraints{}, nil 154 | } 155 | 156 | func (offConstr *offConstraints) IsAllowed(level LogLevel) bool { 157 | return false 158 | } 159 | 160 | func (offConstr *offConstraints) String() string { 161 | return "Off constraint" 162 | } 163 | -------------------------------------------------------------------------------- /common_constraints_test.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "testing" 29 | ) 30 | 31 | func TestInvalidminMaxConstraints(t *testing.T) { 32 | constr, err := NewMinMaxConstraints(CriticalLvl, WarnLvl) 33 | 34 | if err == nil || constr != nil { 35 | t.Errorf("expected an error and a nil value for minmax constraints: min = %d, max = %d. Got: %v, %v", 36 | CriticalLvl, WarnLvl, err, constr) 37 | return 38 | } 39 | } 40 | 41 | func TestInvalidLogLevels(t *testing.T) { 42 | var invalidMin uint8 = 123 43 | var invalidMax uint8 = 124 44 | minMaxConstr, errMinMax := NewMinMaxConstraints(LogLevel(invalidMin), LogLevel(invalidMax)) 45 | 46 | if errMinMax == nil || minMaxConstr != nil { 47 | t.Errorf("expected an error and a nil value for minmax constraints: min = %d, max = %d. Got: %v, %v", 48 | invalidMin, invalidMax, errMinMax, minMaxConstr) 49 | return 50 | } 51 | 52 | invalidList := []LogLevel{145} 53 | 54 | listConstr, errList := NewListConstraints(invalidList) 55 | 56 | if errList == nil || listConstr != nil { 57 | t.Errorf("expected an error and a nil value for constraints list: %v. Got: %v, %v", 58 | invalidList, errList, listConstr) 59 | return 60 | } 61 | } 62 | 63 | func TestlistConstraintsWithDuplicates(t *testing.T) { 64 | duplicateList := []LogLevel{TraceLvl, DebugLvl, InfoLvl, 65 | WarnLvl, ErrorLvl, CriticalLvl, CriticalLvl, CriticalLvl} 66 | 67 | listConstr, errList := NewListConstraints(duplicateList) 68 | 69 | if errList != nil || listConstr == nil { 70 | t.Errorf("expected a valid constraints list struct for: %v, got error: %v, value: %v", 71 | duplicateList, errList, listConstr) 72 | return 73 | } 74 | 75 | listLevels := listConstr.AllowedLevels() 76 | 77 | if listLevels == nil { 78 | t.Fatalf("listConstr.AllowedLevels() == nil") 79 | return 80 | } 81 | 82 | if len(listLevels) != 6 { 83 | t.Errorf("expected: listConstr.AllowedLevels() length == 6. Got: %d", len(listLevels)) 84 | return 85 | } 86 | } 87 | 88 | func TestlistConstraintsWithOffInList(t *testing.T) { 89 | offList := []LogLevel{TraceLvl, DebugLvl, Off} 90 | 91 | listConstr, errList := NewListConstraints(offList) 92 | 93 | if errList == nil || listConstr != nil { 94 | t.Errorf("expected an error and a nil value for constraints list with 'Off': %v. Got: %v, %v", 95 | offList, errList, listConstr) 96 | return 97 | } 98 | } 99 | 100 | type logLevelTestCase struct { 101 | level LogLevel 102 | allowed bool 103 | } 104 | 105 | var minMaxTests = []logLevelTestCase{ 106 | {TraceLvl, false}, 107 | {DebugLvl, false}, 108 | {InfoLvl, true}, 109 | {WarnLvl, true}, 110 | {ErrorLvl, false}, 111 | {CriticalLvl, false}, 112 | {123, false}, 113 | {6, false}, 114 | } 115 | 116 | func TestValidminMaxConstraints(t *testing.T) { 117 | 118 | constr, err := NewMinMaxConstraints(InfoLvl, WarnLvl) 119 | 120 | if err != nil || constr == nil { 121 | t.Errorf("expected a valid constraints struct for minmax constraints: min = %d, max = %d. Got: %v, %v", 122 | InfoLvl, WarnLvl, err, constr) 123 | return 124 | } 125 | 126 | for _, minMaxTest := range minMaxTests { 127 | allowed := constr.IsAllowed(minMaxTest.level) 128 | if allowed != minMaxTest.allowed { 129 | t.Errorf("expected IsAllowed() = %t for level = %d. Got: %t", 130 | minMaxTest.allowed, minMaxTest.level, allowed) 131 | return 132 | } 133 | } 134 | } 135 | 136 | var listTests = []logLevelTestCase{ 137 | {TraceLvl, true}, 138 | {DebugLvl, false}, 139 | {InfoLvl, true}, 140 | {WarnLvl, true}, 141 | {ErrorLvl, false}, 142 | {CriticalLvl, true}, 143 | {123, false}, 144 | {6, false}, 145 | } 146 | 147 | func TestValidlistConstraints(t *testing.T) { 148 | validList := []LogLevel{TraceLvl, InfoLvl, WarnLvl, CriticalLvl} 149 | constr, err := NewListConstraints(validList) 150 | 151 | if err != nil || constr == nil { 152 | t.Errorf("expected a valid constraints list struct for: %v. Got error: %v, value: %v", 153 | validList, err, constr) 154 | return 155 | } 156 | 157 | for _, minMaxTest := range listTests { 158 | allowed := constr.IsAllowed(minMaxTest.level) 159 | if allowed != minMaxTest.allowed { 160 | t.Errorf("expected IsAllowed() = %t for level = %d. Got: %t", 161 | minMaxTest.allowed, minMaxTest.level, allowed) 162 | return 163 | } 164 | } 165 | } 166 | 167 | var offTests = []logLevelTestCase{ 168 | {TraceLvl, false}, 169 | {DebugLvl, false}, 170 | {InfoLvl, false}, 171 | {WarnLvl, false}, 172 | {ErrorLvl, false}, 173 | {CriticalLvl, false}, 174 | {123, false}, 175 | {6, false}, 176 | } 177 | 178 | func TestValidListoffConstraints(t *testing.T) { 179 | validList := []LogLevel{Off} 180 | constr, err := NewListConstraints(validList) 181 | 182 | if err != nil || constr == nil { 183 | t.Errorf("expected a valid constraints list struct for: %v. Got error: %v, value: %v", 184 | validList, err, constr) 185 | return 186 | } 187 | 188 | for _, minMaxTest := range offTests { 189 | allowed := constr.IsAllowed(minMaxTest.level) 190 | if allowed != minMaxTest.allowed { 191 | t.Errorf("expected IsAllowed() = %t for level = %d. Got: %t", 192 | minMaxTest.allowed, minMaxTest.level, allowed) 193 | return 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /common_context.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "errors" 29 | "fmt" 30 | "os" 31 | "path/filepath" 32 | "runtime" 33 | "strings" 34 | "sync" 35 | "time" 36 | ) 37 | 38 | var ( 39 | workingDir = "/" 40 | stackCache map[uintptr]*logContext 41 | stackCacheLock sync.RWMutex 42 | ) 43 | 44 | func init() { 45 | wd, err := os.Getwd() 46 | if err == nil { 47 | workingDir = filepath.ToSlash(wd) + "/" 48 | } 49 | stackCache = make(map[uintptr]*logContext) 50 | } 51 | 52 | // Represents runtime caller context. 53 | type LogContextInterface interface { 54 | // Caller's function name. 55 | Func() string 56 | // Caller's line number. 57 | Line() int 58 | // Caller's file short path (in slashed form). 59 | ShortPath() string 60 | // Caller's file full path (in slashed form). 61 | FullPath() string 62 | // Caller's file name (without path). 63 | FileName() string 64 | // True if the context is correct and may be used. 65 | // If false, then an error in context evaluation occurred and 66 | // all its other data may be corrupted. 67 | IsValid() bool 68 | // Time when log function was called. 69 | CallTime() time.Time 70 | // Custom context that can be set by calling logger.SetContext 71 | CustomContext() interface{} 72 | } 73 | 74 | // Returns context of the caller 75 | func currentContext(custom interface{}) (LogContextInterface, error) { 76 | return specifyContext(1, custom) 77 | } 78 | 79 | func extractCallerInfo(skip int) (*logContext, error) { 80 | var stack [1]uintptr 81 | if runtime.Callers(skip+1, stack[:]) != 1 { 82 | return nil, errors.New("error during runtime.Callers") 83 | } 84 | pc := stack[0] 85 | 86 | // do we have a cache entry? 87 | stackCacheLock.RLock() 88 | ctx, ok := stackCache[pc] 89 | stackCacheLock.RUnlock() 90 | if ok { 91 | return ctx, nil 92 | } 93 | 94 | // look up the details of the given caller 95 | funcInfo := runtime.FuncForPC(pc) 96 | if funcInfo == nil { 97 | return nil, errors.New("error during runtime.FuncForPC") 98 | } 99 | 100 | var shortPath string 101 | fullPath, line := funcInfo.FileLine(pc) 102 | if strings.HasPrefix(fullPath, workingDir) { 103 | shortPath = fullPath[len(workingDir):] 104 | } else { 105 | shortPath = fullPath 106 | } 107 | funcName := funcInfo.Name() 108 | if strings.HasPrefix(funcName, workingDir) { 109 | funcName = funcName[len(workingDir):] 110 | } 111 | 112 | ctx = &logContext{ 113 | funcName: funcName, 114 | line: line, 115 | shortPath: shortPath, 116 | fullPath: fullPath, 117 | fileName: filepath.Base(fullPath), 118 | } 119 | 120 | // save the details in the cache; note that it's possible we might 121 | // have written an entry into the map in between the test above and 122 | // this section, but the behaviour is still correct 123 | stackCacheLock.Lock() 124 | stackCache[pc] = ctx 125 | stackCacheLock.Unlock() 126 | return ctx, nil 127 | } 128 | 129 | // Returns context of the function with placed "skip" stack frames of the caller 130 | // If skip == 0 then behaves like currentContext 131 | // Context is returned in any situation, even if error occurs. But, if an error 132 | // occurs, the returned context is an error context, which contains no paths 133 | // or names, but states that they can't be extracted. 134 | func specifyContext(skip int, custom interface{}) (LogContextInterface, error) { 135 | callTime := time.Now() 136 | if skip < 0 { 137 | err := fmt.Errorf("can not skip negative stack frames") 138 | return &errorContext{callTime, err}, err 139 | } 140 | caller, err := extractCallerInfo(skip + 2) 141 | if err != nil { 142 | return &errorContext{callTime, err}, err 143 | } 144 | ctx := new(logContext) 145 | *ctx = *caller 146 | ctx.callTime = callTime 147 | ctx.custom = custom 148 | return ctx, nil 149 | } 150 | 151 | // Represents a normal runtime caller context. 152 | type logContext struct { 153 | funcName string 154 | line int 155 | shortPath string 156 | fullPath string 157 | fileName string 158 | callTime time.Time 159 | custom interface{} 160 | } 161 | 162 | func (context *logContext) IsValid() bool { 163 | return true 164 | } 165 | 166 | func (context *logContext) Func() string { 167 | return context.funcName 168 | } 169 | 170 | func (context *logContext) Line() int { 171 | return context.line 172 | } 173 | 174 | func (context *logContext) ShortPath() string { 175 | return context.shortPath 176 | } 177 | 178 | func (context *logContext) FullPath() string { 179 | return context.fullPath 180 | } 181 | 182 | func (context *logContext) FileName() string { 183 | return context.fileName 184 | } 185 | 186 | func (context *logContext) CallTime() time.Time { 187 | return context.callTime 188 | } 189 | 190 | func (context *logContext) CustomContext() interface{} { 191 | return context.custom 192 | } 193 | 194 | // Represents an error context 195 | type errorContext struct { 196 | errorTime time.Time 197 | err error 198 | } 199 | 200 | func (errContext *errorContext) getErrorText(prefix string) string { 201 | return fmt.Sprintf("%s() error: %s", prefix, errContext.err) 202 | } 203 | 204 | func (errContext *errorContext) IsValid() bool { 205 | return false 206 | } 207 | 208 | func (errContext *errorContext) Line() int { 209 | return -1 210 | } 211 | 212 | func (errContext *errorContext) Func() string { 213 | return errContext.getErrorText("Func") 214 | } 215 | 216 | func (errContext *errorContext) ShortPath() string { 217 | return errContext.getErrorText("ShortPath") 218 | } 219 | 220 | func (errContext *errorContext) FullPath() string { 221 | return errContext.getErrorText("FullPath") 222 | } 223 | 224 | func (errContext *errorContext) FileName() string { 225 | return errContext.getErrorText("FileName") 226 | } 227 | 228 | func (errContext *errorContext) CallTime() time.Time { 229 | return errContext.errorTime 230 | } 231 | 232 | func (errContext *errorContext) CustomContext() interface{} { 233 | return nil 234 | } 235 | -------------------------------------------------------------------------------- /common_context_test.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "os" 29 | "path/filepath" 30 | "strings" 31 | "testing" 32 | ) 33 | 34 | const ( 35 | testShortPath = "common_context_test.go" 36 | ) 37 | 38 | var ( 39 | commonPrefix string 40 | testFullPath string 41 | ) 42 | 43 | func init() { 44 | // Here we remove the hardcoding of the package name which 45 | // may break forks and some CI environments such as jenkins. 46 | ctx, _ := extractCallerInfo(1) 47 | funcName := ctx.funcName 48 | preIndex := strings.Index(funcName, "init·") 49 | if preIndex == -1 { 50 | preIndex = strings.Index(funcName, "init") 51 | } 52 | commonPrefix = funcName[:preIndex] 53 | wd, err := os.Getwd() 54 | if err == nil { 55 | // Transform the file path into a slashed form: 56 | // This is the proper platform-neutral way. 57 | testFullPath = filepath.ToSlash(filepath.Join(wd, testShortPath)) 58 | } 59 | } 60 | 61 | func TestContext(t *testing.T) { 62 | context, err := currentContext(nil) 63 | if err != nil { 64 | t.Fatalf("unexpected error: %s", err) 65 | } 66 | if context == nil { 67 | t.Fatalf("unexpected error: context is nil") 68 | } 69 | if fn, funcName := context.Func(), commonPrefix+"TestContext"; fn != funcName { 70 | // Account for a case when the func full path is longer than commonPrefix but includes it. 71 | if !strings.HasSuffix(fn, funcName) { 72 | t.Errorf("expected context.Func == %s ; got %s", funcName, context.Func()) 73 | } 74 | } 75 | if context.ShortPath() != testShortPath { 76 | t.Errorf("expected context.ShortPath == %s ; got %s", testShortPath, context.ShortPath()) 77 | } 78 | if len(testFullPath) == 0 { 79 | t.Fatal("working directory seems invalid") 80 | } 81 | if context.FullPath() != testFullPath { 82 | t.Errorf("expected context.FullPath == %s ; got %s", testFullPath, context.FullPath()) 83 | } 84 | } 85 | 86 | func innerContext() (context LogContextInterface, err error) { 87 | return currentContext(nil) 88 | } 89 | 90 | func TestInnerContext(t *testing.T) { 91 | context, err := innerContext() 92 | if err != nil { 93 | t.Fatalf("unexpected error: %s", err) 94 | } 95 | if context == nil { 96 | t.Fatalf("unexpected error: context is nil") 97 | } 98 | if fn, funcName := context.Func(), commonPrefix+"innerContext"; fn != funcName { 99 | // Account for a case when the func full path is longer than commonPrefix but includes it. 100 | if !strings.HasSuffix(fn, funcName) { 101 | t.Errorf("expected context.Func == %s ; got %s", funcName, context.Func()) 102 | } 103 | } 104 | if context.ShortPath() != testShortPath { 105 | t.Errorf("expected context.ShortPath == %s ; got %s", testShortPath, context.ShortPath()) 106 | } 107 | if len(testFullPath) == 0 { 108 | t.Fatal("working directory seems invalid") 109 | } 110 | if context.FullPath() != testFullPath { 111 | t.Errorf("expected context.FullPath == %s ; got %s", testFullPath, context.FullPath()) 112 | } 113 | } 114 | 115 | type testContext struct { 116 | field string 117 | } 118 | 119 | func TestCustomContext(t *testing.T) { 120 | expected := "testStr" 121 | context, err := currentContext(&testContext{expected}) 122 | if err != nil { 123 | t.Fatalf("unexpected error: %s", err) 124 | } 125 | if st, _ := context.CustomContext().(*testContext); st.field != expected { 126 | t.Errorf("expected context.CustomContext == %s ; got %s", expected, st.field) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /common_exception.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "errors" 29 | "fmt" 30 | "regexp" 31 | "strings" 32 | ) 33 | 34 | // Used in rules creation to validate input file and func filters 35 | var ( 36 | fileFormatValidator = regexp.MustCompile(`[a-zA-Z0-9\\/ _\*\.]*`) 37 | funcFormatValidator = regexp.MustCompile(`[a-zA-Z0-9_\*\.]*`) 38 | ) 39 | 40 | // LogLevelException represents an exceptional case used when you need some specific files or funcs to 41 | // override general constraints and to use their own. 42 | type LogLevelException struct { 43 | funcPatternParts []string 44 | filePatternParts []string 45 | 46 | funcPattern string 47 | filePattern string 48 | 49 | constraints logLevelConstraints 50 | } 51 | 52 | // NewLogLevelException creates a new exception. 53 | func NewLogLevelException(funcPattern string, filePattern string, constraints logLevelConstraints) (*LogLevelException, error) { 54 | if constraints == nil { 55 | return nil, errors.New("constraints can not be nil") 56 | } 57 | 58 | exception := new(LogLevelException) 59 | 60 | err := exception.initFuncPatternParts(funcPattern) 61 | if err != nil { 62 | return nil, err 63 | } 64 | exception.funcPattern = strings.Join(exception.funcPatternParts, "") 65 | 66 | err = exception.initFilePatternParts(filePattern) 67 | if err != nil { 68 | return nil, err 69 | } 70 | exception.filePattern = strings.Join(exception.filePatternParts, "") 71 | 72 | exception.constraints = constraints 73 | 74 | return exception, nil 75 | } 76 | 77 | // MatchesContext returns true if context matches the patterns of this LogLevelException 78 | func (logLevelEx *LogLevelException) MatchesContext(context LogContextInterface) bool { 79 | return logLevelEx.match(context.Func(), context.FullPath()) 80 | } 81 | 82 | // IsAllowed returns true if log level is allowed according to the constraints of this LogLevelException 83 | func (logLevelEx *LogLevelException) IsAllowed(level LogLevel) bool { 84 | return logLevelEx.constraints.IsAllowed(level) 85 | } 86 | 87 | // FuncPattern returns the function pattern of a exception 88 | func (logLevelEx *LogLevelException) FuncPattern() string { 89 | return logLevelEx.funcPattern 90 | } 91 | 92 | // FuncPattern returns the file pattern of a exception 93 | func (logLevelEx *LogLevelException) FilePattern() string { 94 | return logLevelEx.filePattern 95 | } 96 | 97 | // initFuncPatternParts checks whether the func filter has a correct format and splits funcPattern on parts 98 | func (logLevelEx *LogLevelException) initFuncPatternParts(funcPattern string) (err error) { 99 | 100 | if funcFormatValidator.FindString(funcPattern) != funcPattern { 101 | return errors.New("func path \"" + funcPattern + "\" contains incorrect symbols. Only a-z A-Z 0-9 _ * . allowed)") 102 | } 103 | 104 | logLevelEx.funcPatternParts = splitPattern(funcPattern) 105 | return nil 106 | } 107 | 108 | // Checks whether the file filter has a correct format and splits file patterns using splitPattern. 109 | func (logLevelEx *LogLevelException) initFilePatternParts(filePattern string) (err error) { 110 | 111 | if fileFormatValidator.FindString(filePattern) != filePattern { 112 | return errors.New("file path \"" + filePattern + "\" contains incorrect symbols. Only a-z A-Z 0-9 \\ / _ * . allowed)") 113 | } 114 | 115 | logLevelEx.filePatternParts = splitPattern(filePattern) 116 | return err 117 | } 118 | 119 | func (logLevelEx *LogLevelException) match(funcPath string, filePath string) bool { 120 | if !stringMatchesPattern(logLevelEx.funcPatternParts, funcPath) { 121 | return false 122 | } 123 | return stringMatchesPattern(logLevelEx.filePatternParts, filePath) 124 | } 125 | 126 | func (logLevelEx *LogLevelException) String() string { 127 | str := fmt.Sprintf("Func: %s File: %s", logLevelEx.funcPattern, logLevelEx.filePattern) 128 | 129 | if logLevelEx.constraints != nil { 130 | str += fmt.Sprintf("Constr: %s", logLevelEx.constraints) 131 | } else { 132 | str += "nil" 133 | } 134 | 135 | return str 136 | } 137 | 138 | // splitPattern splits pattern into strings and asterisks. Example: "ab*cde**f" -> ["ab", "*", "cde", "*", "f"] 139 | func splitPattern(pattern string) []string { 140 | var patternParts []string 141 | var lastChar rune 142 | for _, char := range pattern { 143 | if char == '*' { 144 | if lastChar != '*' { 145 | patternParts = append(patternParts, "*") 146 | } 147 | } else { 148 | if len(patternParts) != 0 && lastChar != '*' { 149 | patternParts[len(patternParts)-1] += string(char) 150 | } else { 151 | patternParts = append(patternParts, string(char)) 152 | } 153 | } 154 | lastChar = char 155 | } 156 | 157 | return patternParts 158 | } 159 | 160 | // stringMatchesPattern check whether testString matches pattern with asterisks. 161 | // Standard regexp functionality is not used here because of performance issues. 162 | func stringMatchesPattern(patternparts []string, testString string) bool { 163 | if len(patternparts) == 0 { 164 | return len(testString) == 0 165 | } 166 | 167 | part := patternparts[0] 168 | if part != "*" { 169 | index := strings.Index(testString, part) 170 | if index == 0 { 171 | return stringMatchesPattern(patternparts[1:], testString[len(part):]) 172 | } 173 | } else { 174 | if len(patternparts) == 1 { 175 | return true 176 | } 177 | 178 | newTestString := testString 179 | part = patternparts[1] 180 | for { 181 | index := strings.Index(newTestString, part) 182 | if index == -1 { 183 | break 184 | } 185 | 186 | newTestString = newTestString[index+len(part):] 187 | result := stringMatchesPattern(patternparts[2:], newTestString) 188 | if result { 189 | return true 190 | } 191 | } 192 | } 193 | return false 194 | } 195 | -------------------------------------------------------------------------------- /common_exception_test.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "testing" 29 | ) 30 | 31 | type exceptionTestCase struct { 32 | funcPattern string 33 | filePattern string 34 | funcName string 35 | fileName string 36 | match bool 37 | } 38 | 39 | var exceptionTestCases = []exceptionTestCase{ 40 | {"*", "*", "func", "file", true}, 41 | {"func*", "*", "func", "file", true}, 42 | {"*func", "*", "func", "file", true}, 43 | {"*func", "*", "1func", "file", true}, 44 | {"func*", "*", "func1", "file", true}, 45 | {"fu*nc", "*", "func", "file", true}, 46 | {"fu*nc", "*", "fu1nc", "file", true}, 47 | {"fu*nc", "*", "func1nc", "file", true}, 48 | {"*fu*nc*", "*", "somefuntonc", "file", true}, 49 | {"fu*nc", "*", "f1nc", "file", false}, 50 | {"func*", "*", "fun", "file", false}, 51 | {"fu*nc", "*", "func1n", "file", false}, 52 | {"**f**u**n**c**", "*", "func1n", "file", true}, 53 | } 54 | 55 | func TestMatchingCorrectness(t *testing.T) { 56 | constraints, err := NewListConstraints([]LogLevel{TraceLvl}) 57 | if err != nil { 58 | t.Error(err) 59 | return 60 | } 61 | 62 | for _, testCase := range exceptionTestCases { 63 | rule, ruleError := NewLogLevelException(testCase.funcPattern, testCase.filePattern, constraints) 64 | if ruleError != nil { 65 | t.Fatalf("Unexpected error on rule creation: [ %v, %v ]. %v", 66 | testCase.funcPattern, testCase.filePattern, ruleError) 67 | } 68 | 69 | match := rule.match(testCase.funcName, testCase.fileName) 70 | if match != testCase.match { 71 | t.Errorf("incorrect matching for [ %v, %v ] [ %v, %v ] Expected: %t. Got: %t", 72 | testCase.funcPattern, testCase.filePattern, testCase.funcName, testCase.fileName, testCase.match, match) 73 | } 74 | } 75 | } 76 | 77 | func TestAsterisksReducing(t *testing.T) { 78 | constraints, err := NewListConstraints([]LogLevel{TraceLvl}) 79 | if err != nil { 80 | t.Error(err) 81 | return 82 | } 83 | 84 | rule, err := NewLogLevelException("***func**", "fi*****le", constraints) 85 | if err != nil { 86 | t.Error(err) 87 | return 88 | } 89 | expectFunc := "*func*" 90 | if rule.FuncPattern() != expectFunc { 91 | t.Errorf("asterisks must be reduced. Expect:%v, Got:%v", expectFunc, rule.FuncPattern()) 92 | } 93 | 94 | expectFile := "fi*le" 95 | if rule.FilePattern() != expectFile { 96 | t.Errorf("asterisks must be reduced. Expect:%v, Got:%v", expectFile, rule.FilePattern()) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /common_flusher.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | // flusherInterface represents all objects that have to do cleanup 28 | // at certain moments of time (e.g. before app shutdown to avoid data loss) 29 | type flusherInterface interface { 30 | Flush() 31 | } 32 | -------------------------------------------------------------------------------- /common_loglevel.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | // Log level type 28 | type LogLevel uint8 29 | 30 | // Log levels 31 | const ( 32 | TraceLvl = iota 33 | DebugLvl 34 | InfoLvl 35 | WarnLvl 36 | ErrorLvl 37 | CriticalLvl 38 | Off 39 | ) 40 | 41 | // Log level string representations (used in configuration files) 42 | const ( 43 | TraceStr = "trace" 44 | DebugStr = "debug" 45 | InfoStr = "info" 46 | WarnStr = "warn" 47 | ErrorStr = "error" 48 | CriticalStr = "critical" 49 | OffStr = "off" 50 | ) 51 | 52 | var levelToStringRepresentations = map[LogLevel]string{ 53 | TraceLvl: TraceStr, 54 | DebugLvl: DebugStr, 55 | InfoLvl: InfoStr, 56 | WarnLvl: WarnStr, 57 | ErrorLvl: ErrorStr, 58 | CriticalLvl: CriticalStr, 59 | Off: OffStr, 60 | } 61 | 62 | // LogLevelFromString parses a string and returns a corresponding log level, if sucessfull. 63 | func LogLevelFromString(levelStr string) (level LogLevel, found bool) { 64 | for lvl, lvlStr := range levelToStringRepresentations { 65 | if lvlStr == levelStr { 66 | return lvl, true 67 | } 68 | } 69 | 70 | return 0, false 71 | } 72 | 73 | // LogLevelToString returns seelog string representation for a specified level. Returns "" for invalid log levels. 74 | func (level LogLevel) String() string { 75 | levelStr, ok := levelToStringRepresentations[level] 76 | if ok { 77 | return levelStr 78 | } 79 | 80 | return "" 81 | } 82 | -------------------------------------------------------------------------------- /dispatch_customdispatcher_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 - 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 | 25 | package seelog 26 | 27 | import ( 28 | "testing" 29 | ) 30 | 31 | type testCustomDispatcherMessageReceiver struct { 32 | customTestReceiver 33 | } 34 | 35 | func TestCustomDispatcher_Message(t *testing.T) { 36 | recName := "TestCustomDispatcher_Message" 37 | RegisterReceiver(recName, &testCustomDispatcherMessageReceiver{}) 38 | 39 | customDispatcher, err := NewCustomReceiverDispatcher(onlyMessageFormatForTest, recName, CustomReceiverInitArgs{ 40 | XmlCustomAttrs: map[string]string{ 41 | "test": "testdata", 42 | }, 43 | }) 44 | if err != nil { 45 | t.Error(err) 46 | return 47 | } 48 | 49 | context, err := currentContext(nil) 50 | if err != nil { 51 | t.Error(err) 52 | return 53 | } 54 | 55 | bytes := []byte("Hello") 56 | customDispatcher.Dispatch(string(bytes), TraceLvl, context, func(err error) {}) 57 | 58 | cout := customDispatcher.innerReceiver.(*testCustomDispatcherMessageReceiver).customTestReceiver.co 59 | if cout.initCalled != true { 60 | t.Error("Init not called") 61 | return 62 | } 63 | if cout.dataPassed != "testdata" { 64 | t.Errorf("wrong data passed: '%s'", cout.dataPassed) 65 | return 66 | } 67 | if cout.messageOutput != string(bytes) { 68 | t.Errorf("wrong message output: '%s'", cout.messageOutput) 69 | return 70 | } 71 | if cout.levelOutput != TraceLvl { 72 | t.Errorf("wrong log level: '%s'", cout.levelOutput) 73 | return 74 | } 75 | if cout.flushed { 76 | t.Error("Flush was not expected") 77 | return 78 | } 79 | if cout.closed { 80 | t.Error("Closing was not expected") 81 | return 82 | } 83 | } 84 | 85 | type testCustomDispatcherFlushReceiver struct { 86 | customTestReceiver 87 | } 88 | 89 | func TestCustomDispatcher_Flush(t *testing.T) { 90 | recName := "TestCustomDispatcher_Flush" 91 | RegisterReceiver(recName, &testCustomDispatcherFlushReceiver{}) 92 | 93 | customDispatcher, err := NewCustomReceiverDispatcher(onlyMessageFormatForTest, recName, CustomReceiverInitArgs{ 94 | XmlCustomAttrs: map[string]string{ 95 | "test": "testdata", 96 | }, 97 | }) 98 | if err != nil { 99 | t.Error(err) 100 | return 101 | } 102 | 103 | customDispatcher.Flush() 104 | 105 | cout := customDispatcher.innerReceiver.(*testCustomDispatcherFlushReceiver).customTestReceiver.co 106 | if cout.initCalled != true { 107 | t.Error("Init not called") 108 | return 109 | } 110 | if cout.dataPassed != "testdata" { 111 | t.Errorf("wrong data passed: '%s'", cout.dataPassed) 112 | return 113 | } 114 | if cout.messageOutput != "" { 115 | t.Errorf("wrong message output: '%s'", cout.messageOutput) 116 | return 117 | } 118 | if cout.levelOutput != TraceLvl { 119 | t.Errorf("wrong log level: '%s'", cout.levelOutput) 120 | return 121 | } 122 | if !cout.flushed { 123 | t.Error("Flush was expected") 124 | return 125 | } 126 | if cout.closed { 127 | t.Error("Closing was not expected") 128 | return 129 | } 130 | } 131 | 132 | type testCustomDispatcherCloseReceiver struct { 133 | customTestReceiver 134 | } 135 | 136 | func TestCustomDispatcher_Close(t *testing.T) { 137 | recName := "TestCustomDispatcher_Close" 138 | RegisterReceiver(recName, &testCustomDispatcherCloseReceiver{}) 139 | 140 | customDispatcher, err := NewCustomReceiverDispatcher(onlyMessageFormatForTest, recName, CustomReceiverInitArgs{ 141 | XmlCustomAttrs: map[string]string{ 142 | "test": "testdata", 143 | }, 144 | }) 145 | if err != nil { 146 | t.Error(err) 147 | return 148 | } 149 | 150 | customDispatcher.Close() 151 | 152 | cout := customDispatcher.innerReceiver.(*testCustomDispatcherCloseReceiver).customTestReceiver.co 153 | if cout.initCalled != true { 154 | t.Error("Init not called") 155 | return 156 | } 157 | if cout.dataPassed != "testdata" { 158 | t.Errorf("wrong data passed: '%s'", cout.dataPassed) 159 | return 160 | } 161 | if cout.messageOutput != "" { 162 | t.Errorf("wrong message output: '%s'", cout.messageOutput) 163 | return 164 | } 165 | if cout.levelOutput != TraceLvl { 166 | t.Errorf("wrong log level: '%s'", cout.levelOutput) 167 | return 168 | } 169 | if !cout.flushed { 170 | t.Error("Flush was expected") 171 | return 172 | } 173 | if !cout.closed { 174 | t.Error("Closing was expected") 175 | return 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /dispatch_dispatcher.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "errors" 29 | "fmt" 30 | "io" 31 | ) 32 | 33 | // A dispatcherInterface is used to dispatch message to all underlying receivers. 34 | // Dispatch logic depends on given context and log level. Any errors are reported using errorFunc. 35 | // Also, as underlying receivers may have a state, dispatcher has a ShuttingDown method which performs 36 | // an immediate cleanup of all data that is stored in the receivers 37 | type dispatcherInterface interface { 38 | flusherInterface 39 | io.Closer 40 | Dispatch(message string, level LogLevel, context LogContextInterface, errorFunc func(err error)) 41 | } 42 | 43 | type dispatcher struct { 44 | formatter *formatter 45 | writers []*formattedWriter 46 | dispatchers []dispatcherInterface 47 | } 48 | 49 | // Creates a dispatcher which dispatches data to a list of receivers. 50 | // Each receiver should be either a Dispatcher or io.Writer, otherwise an error will be returned 51 | func createDispatcher(formatter *formatter, receivers []interface{}) (*dispatcher, error) { 52 | if formatter == nil { 53 | return nil, errors.New("formatter cannot be nil") 54 | } 55 | if receivers == nil || len(receivers) == 0 { 56 | return nil, errors.New("receivers cannot be nil or empty") 57 | } 58 | 59 | disp := &dispatcher{formatter, make([]*formattedWriter, 0), make([]dispatcherInterface, 0)} 60 | for _, receiver := range receivers { 61 | writer, ok := receiver.(*formattedWriter) 62 | if ok { 63 | disp.writers = append(disp.writers, writer) 64 | continue 65 | } 66 | 67 | ioWriter, ok := receiver.(io.Writer) 68 | if ok { 69 | writer, err := NewFormattedWriter(ioWriter, disp.formatter) 70 | if err != nil { 71 | return nil, err 72 | } 73 | disp.writers = append(disp.writers, writer) 74 | continue 75 | } 76 | 77 | dispInterface, ok := receiver.(dispatcherInterface) 78 | if ok { 79 | disp.dispatchers = append(disp.dispatchers, dispInterface) 80 | continue 81 | } 82 | 83 | return nil, errors.New("method can receive either io.Writer or dispatcherInterface") 84 | } 85 | 86 | return disp, nil 87 | } 88 | 89 | func (disp *dispatcher) Dispatch( 90 | message string, 91 | level LogLevel, 92 | context LogContextInterface, 93 | errorFunc func(err error)) { 94 | 95 | for _, writer := range disp.writers { 96 | err := writer.Write(message, level, context) 97 | if err != nil { 98 | errorFunc(err) 99 | } 100 | } 101 | 102 | for _, dispInterface := range disp.dispatchers { 103 | dispInterface.Dispatch(message, level, context, errorFunc) 104 | } 105 | } 106 | 107 | // Flush goes through all underlying writers which implement flusherInterface interface 108 | // and closes them. Recursively performs the same action for underlying dispatchers 109 | func (disp *dispatcher) Flush() { 110 | for _, disp := range disp.Dispatchers() { 111 | disp.Flush() 112 | } 113 | 114 | for _, formatWriter := range disp.Writers() { 115 | flusher, ok := formatWriter.Writer().(flusherInterface) 116 | if ok { 117 | flusher.Flush() 118 | } 119 | } 120 | } 121 | 122 | // Close goes through all underlying writers which implement io.Closer interface 123 | // and closes them. Recursively performs the same action for underlying dispatchers 124 | // Before closing, writers are flushed to prevent loss of any buffered data, so 125 | // a call to Flush() func before Close() is not necessary 126 | func (disp *dispatcher) Close() error { 127 | for _, disp := range disp.Dispatchers() { 128 | disp.Flush() 129 | err := disp.Close() 130 | if err != nil { 131 | return err 132 | } 133 | } 134 | 135 | for _, formatWriter := range disp.Writers() { 136 | flusher, ok := formatWriter.Writer().(flusherInterface) 137 | if ok { 138 | flusher.Flush() 139 | } 140 | 141 | closer, ok := formatWriter.Writer().(io.Closer) 142 | if ok { 143 | err := closer.Close() 144 | if err != nil { 145 | return err 146 | } 147 | } 148 | } 149 | 150 | return nil 151 | } 152 | 153 | func (disp *dispatcher) Writers() []*formattedWriter { 154 | return disp.writers 155 | } 156 | 157 | func (disp *dispatcher) Dispatchers() []dispatcherInterface { 158 | return disp.dispatchers 159 | } 160 | 161 | func (disp *dispatcher) String() string { 162 | str := "formatter: " + disp.formatter.String() + "\n" 163 | 164 | str += " ->Dispatchers:" 165 | 166 | if len(disp.dispatchers) == 0 { 167 | str += "none\n" 168 | } else { 169 | str += "\n" 170 | 171 | for _, disp := range disp.dispatchers { 172 | str += fmt.Sprintf(" ->%s", disp) 173 | } 174 | } 175 | 176 | str += " ->Writers:" 177 | 178 | if len(disp.writers) == 0 { 179 | str += "none\n" 180 | } else { 181 | str += "\n" 182 | 183 | for _, writer := range disp.writers { 184 | str += fmt.Sprintf(" ->%s\n", writer) 185 | } 186 | } 187 | 188 | return str 189 | } 190 | -------------------------------------------------------------------------------- /dispatch_filterdispatcher.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "fmt" 29 | ) 30 | 31 | // A filterDispatcher writes the given message to underlying receivers only if message log level 32 | // is in the allowed list. 33 | type filterDispatcher struct { 34 | *dispatcher 35 | allowList map[LogLevel]bool 36 | } 37 | 38 | // NewFilterDispatcher creates a new filterDispatcher using a list of allowed levels. 39 | func NewFilterDispatcher(formatter *formatter, receivers []interface{}, allowList ...LogLevel) (*filterDispatcher, error) { 40 | disp, err := createDispatcher(formatter, receivers) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | allows := make(map[LogLevel]bool) 46 | for _, allowLevel := range allowList { 47 | allows[allowLevel] = true 48 | } 49 | 50 | return &filterDispatcher{disp, allows}, nil 51 | } 52 | 53 | func (filter *filterDispatcher) Dispatch( 54 | message string, 55 | level LogLevel, 56 | context LogContextInterface, 57 | errorFunc func(err error)) { 58 | isAllowed, ok := filter.allowList[level] 59 | if ok && isAllowed { 60 | filter.dispatcher.Dispatch(message, level, context, errorFunc) 61 | } 62 | } 63 | 64 | func (filter *filterDispatcher) String() string { 65 | return fmt.Sprintf("filterDispatcher ->\n%s", filter.dispatcher) 66 | } 67 | -------------------------------------------------------------------------------- /dispatch_filterdispatcher_test.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "testing" 29 | ) 30 | 31 | func TestfilterDispatcher_Pass(t *testing.T) { 32 | writer, _ := newBytesVerifier(t) 33 | filter, err := NewFilterDispatcher(onlyMessageFormatForTest, []interface{}{writer}, TraceLvl) 34 | if err != nil { 35 | t.Error(err) 36 | return 37 | } 38 | 39 | context, err := currentContext(nil) 40 | if err != nil { 41 | t.Error(err) 42 | return 43 | } 44 | 45 | bytes := []byte("Hello") 46 | writer.ExpectBytes(bytes) 47 | filter.Dispatch(string(bytes), TraceLvl, context, func(err error) {}) 48 | writer.MustNotExpect() 49 | } 50 | 51 | func TestfilterDispatcher_Deny(t *testing.T) { 52 | writer, _ := newBytesVerifier(t) 53 | filter, err := NewFilterDispatcher(DefaultFormatter, []interface{}{writer}) 54 | if err != nil { 55 | t.Error(err) 56 | return 57 | } 58 | 59 | context, err := currentContext(nil) 60 | if err != nil { 61 | t.Error(err) 62 | return 63 | } 64 | 65 | bytes := []byte("Hello") 66 | filter.Dispatch(string(bytes), TraceLvl, context, func(err error) {}) 67 | } 68 | -------------------------------------------------------------------------------- /dispatch_splitdispatcher.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "fmt" 29 | ) 30 | 31 | // A splitDispatcher just writes the given message to underlying receivers. (Splits the message stream.) 32 | type splitDispatcher struct { 33 | *dispatcher 34 | } 35 | 36 | func NewSplitDispatcher(formatter *formatter, receivers []interface{}) (*splitDispatcher, error) { 37 | disp, err := createDispatcher(formatter, receivers) 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | return &splitDispatcher{disp}, nil 43 | } 44 | 45 | func (splitter *splitDispatcher) String() string { 46 | return fmt.Sprintf("splitDispatcher ->\n%s", splitter.dispatcher.String()) 47 | } 48 | -------------------------------------------------------------------------------- /dispatch_splitdispatcher_test.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "fmt" 29 | "testing" 30 | ) 31 | 32 | var onlyMessageFormatForTest *formatter 33 | 34 | func init() { 35 | var err error 36 | onlyMessageFormatForTest, err = NewFormatter("%Msg") 37 | if err != nil { 38 | fmt.Println("Can not create only message format: " + err.Error()) 39 | } 40 | } 41 | 42 | func TestsplitDispatcher(t *testing.T) { 43 | writer1, _ := newBytesVerifier(t) 44 | writer2, _ := newBytesVerifier(t) 45 | spliter, err := NewSplitDispatcher(onlyMessageFormatForTest, []interface{}{writer1, writer2}) 46 | if err != nil { 47 | t.Error(err) 48 | return 49 | } 50 | 51 | context, err := currentContext(nil) 52 | if err != nil { 53 | t.Error(err) 54 | return 55 | } 56 | 57 | bytes := []byte("Hello") 58 | 59 | writer1.ExpectBytes(bytes) 60 | writer2.ExpectBytes(bytes) 61 | spliter.Dispatch(string(bytes), TraceLvl, context, func(err error) {}) 62 | writer1.MustNotExpect() 63 | writer2.MustNotExpect() 64 | } 65 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 - 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 | 25 | /* 26 | Package seelog implements logging functionality with flexible dispatching, filtering, and formatting. 27 | 28 | Creation 29 | 30 | To create a logger, use one of the following constructors: 31 | func LoggerFromConfigAsBytes 32 | func LoggerFromConfigAsFile 33 | func LoggerFromConfigAsString 34 | func LoggerFromWriterWithMinLevel 35 | func LoggerFromWriterWithMinLevelAndFormat 36 | func LoggerFromCustomReceiver (check https://github.com/cihub/seelog/wiki/Custom-receivers) 37 | Example: 38 | import log "github.com/cihub/seelog" 39 | 40 | func main() { 41 | logger, err := log.LoggerFromConfigAsFile("seelog.xml") 42 | if err != nil { 43 | panic(err) 44 | } 45 | defer logger.Flush() 46 | ... use logger ... 47 | } 48 | The "defer" line is important because if you are using asynchronous logger behavior, without this line you may end up losing some 49 | messages when you close your application because they are processed in another non-blocking goroutine. To avoid that you 50 | explicitly defer flushing all messages before closing. 51 | 52 | Usage 53 | 54 | Logger created using one of the LoggerFrom* funcs can be used directly by calling one of the main log funcs. 55 | Example: 56 | import log "github.com/cihub/seelog" 57 | 58 | func main() { 59 | logger, err := log.LoggerFromConfigAsFile("seelog.xml") 60 | if err != nil { 61 | panic(err) 62 | } 63 | defer logger.Flush() 64 | logger.Trace("test") 65 | logger.Debugf("var = %s", "abc") 66 | } 67 | 68 | Having loggers as variables is convenient if you are writing your own package with internal logging or if you have 69 | several loggers with different options. 70 | But for most standalone apps it is more convenient to use package level funcs and vars. There is a package level 71 | var 'Current' made for it. You can replace it with another logger using 'ReplaceLogger' and then use package level funcs: 72 | import log "github.com/cihub/seelog" 73 | 74 | func main() { 75 | logger, err := log.LoggerFromConfigAsFile("seelog.xml") 76 | if err != nil { 77 | panic(err) 78 | } 79 | log.ReplaceLogger(logger) 80 | defer log.Flush() 81 | log.Trace("test") 82 | log.Debugf("var = %s", "abc") 83 | } 84 | Last lines 85 | log.Trace("test") 86 | log.Debugf("var = %s", "abc") 87 | do the same as 88 | log.Current.Trace("test") 89 | log.Current.Debugf("var = %s", "abc") 90 | In this example the 'Current' logger was replaced using a 'ReplaceLogger' call and became equal to 'logger' variable created from config. 91 | This way you are able to use package level funcs instead of passing the logger variable. 92 | 93 | Configuration 94 | 95 | Main seelog point is to configure logger via config files and not the code. 96 | The configuration is read by LoggerFrom* funcs. These funcs read xml configuration from different sources and try 97 | to create a logger using it. 98 | 99 | All the configuration features are covered in detail in the official wiki: https://github.com/cihub/seelog/wiki. 100 | There are many sections covering different aspects of seelog, but the most important for understanding configs are: 101 | https://github.com/cihub/seelog/wiki/Constraints-and-exceptions 102 | https://github.com/cihub/seelog/wiki/Dispatchers-and-receivers 103 | https://github.com/cihub/seelog/wiki/Formatting 104 | https://github.com/cihub/seelog/wiki/Logger-types 105 | After you understand these concepts, check the 'Reference' section on the main wiki page to get the up-to-date 106 | list of dispatchers, receivers, formats, and logger types. 107 | 108 | Here is an example config with all these features: 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | This config represents a logger with adaptive timeout between log messages (check logger types reference) which 131 | logs to console, all.log, and errors.log depending on the log level. Its output formats also depend on log level. This logger will only 132 | use log level 'debug' and higher (minlevel is set) for all files with names that don't start with 'test'. For files starting with 'test' 133 | this logger prohibits all levels below 'error'. 134 | 135 | Configuration using code 136 | 137 | Although configuration using code is not recommended, it is sometimes needed and it is possible to do with seelog. Basically, what 138 | you need to do to get started is to create constraints, exceptions and a dispatcher tree (same as with config). Most of the New* 139 | functions in this package are used to provide such capabilities. 140 | 141 | Here is an example of configuration in code, that demonstrates an async loop logger that logs to a simple split dispatcher with 142 | a console receiver using a specified format and is filtered using a top-level min-max constraints and one expection for 143 | the 'main.go' file. So, this is basically a demonstration of configuration of most of the features: 144 | 145 | package main 146 | 147 | import log "github.com/cihub/seelog" 148 | 149 | func main() { 150 | defer log.Flush() 151 | log.Info("Hello from Seelog!") 152 | 153 | consoleWriter, _ := log.NewConsoleWriter() 154 | formatter, _ := log.NewFormatter("%Level %Msg %File%n") 155 | root, _ := log.NewSplitDispatcher(formatter, []interface{}{consoleWriter}) 156 | constraints, _ := log.NewMinMaxConstraints(log.TraceLvl, log.CriticalLvl) 157 | specificConstraints, _ := log.NewListConstraints([]log.LogLevel{log.InfoLvl, log.ErrorLvl}) 158 | ex, _ := log.NewLogLevelException("*", "*main.go", specificConstraints) 159 | exceptions := []*log.LogLevelException{ex} 160 | 161 | logger := log.NewAsyncLoopLogger(log.NewLoggerConfig(constraints, exceptions, root)) 162 | log.ReplaceLogger(logger) 163 | 164 | log.Trace("This should not be seen") 165 | log.Debug("This should not be seen") 166 | log.Info("Test") 167 | log.Error("Test2") 168 | } 169 | 170 | Examples 171 | 172 | To learn seelog features faster you should check the examples package: https://github.com/cihub/seelog-examples 173 | It contains many example configs and usecases. 174 | */ 175 | package seelog 176 | -------------------------------------------------------------------------------- /format_test.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "fmt" 29 | "strings" 30 | "testing" 31 | "time" 32 | ) 33 | 34 | const ( 35 | TestFuncName = "TestFormats" 36 | ) 37 | 38 | type formatTest struct { 39 | formatString string 40 | input string 41 | inputLogLevel LogLevel 42 | expectedOutput string 43 | errorExpected bool 44 | } 45 | 46 | var formatTests = []formatTest{ 47 | {"test", "abcdef", TraceLvl, "test", false}, 48 | {"", "abcdef", TraceLvl, "", false}, 49 | {"%Level", "", TraceLvl, "Trace", false}, 50 | {"%Level", "", DebugLvl, "Debug", false}, 51 | {"%Level", "", InfoLvl, "Info", false}, 52 | {"%Level", "", WarnLvl, "Warn", false}, 53 | {"%Level", "", ErrorLvl, "Error", false}, 54 | {"%Level", "", CriticalLvl, "Critical", false}, 55 | {"[%Level]", "", TraceLvl, "[Trace]", false}, 56 | {"[%Level]", "abc", DebugLvl, "[Debug]", false}, 57 | {"%LevelLevel", "", InfoLvl, "InfoLevel", false}, 58 | {"[%Level][%Level]", "", WarnLvl, "[Warn][Warn]", false}, 59 | {"[%Level]X[%Level]", "", ErrorLvl, "[Error]X[Error]", false}, 60 | {"%Levelll", "", CriticalLvl, "Criticalll", false}, 61 | {"%Lvl", "", TraceLvl, "", true}, 62 | {"%%Level", "", DebugLvl, "%Level", false}, 63 | {"%Level%", "", InfoLvl, "", true}, 64 | {"%sevel", "", WarnLvl, "", true}, 65 | {"Level", "", ErrorLvl, "Level", false}, 66 | {"%LevelLevel", "", CriticalLvl, "CriticalLevel", false}, 67 | {"%Lev", "", TraceLvl, "Trc", false}, 68 | {"%Lev", "", DebugLvl, "Dbg", false}, 69 | {"%Lev", "", InfoLvl, "Inf", false}, 70 | {"%Lev", "", WarnLvl, "Wrn", false}, 71 | {"%Lev", "", ErrorLvl, "Err", false}, 72 | {"%Lev", "", CriticalLvl, "Crt", false}, 73 | {"[%Lev]", "", TraceLvl, "[Trc]", false}, 74 | {"[%Lev]", "abc", DebugLvl, "[Dbg]", false}, 75 | {"%LevLevel", "", InfoLvl, "InfLevel", false}, 76 | {"[%Level][%Lev]", "", WarnLvl, "[Warn][Wrn]", false}, 77 | {"[%Lev]X[%Lev]", "", ErrorLvl, "[Err]X[Err]", false}, 78 | {"%Levll", "", CriticalLvl, "Crtll", false}, 79 | {"%LEVEL", "", TraceLvl, "TRACE", false}, 80 | {"%LEVEL", "", DebugLvl, "DEBUG", false}, 81 | {"%LEVEL", "", InfoLvl, "INFO", false}, 82 | {"%LEVEL", "", WarnLvl, "WARN", false}, 83 | {"%LEVEL", "", ErrorLvl, "ERROR", false}, 84 | {"%LEVEL", "", CriticalLvl, "CRITICAL", false}, 85 | {"[%LEVEL]", "", TraceLvl, "[TRACE]", false}, 86 | {"[%LEVEL]", "abc", DebugLvl, "[DEBUG]", false}, 87 | {"%LEVELLEVEL", "", InfoLvl, "INFOLEVEL", false}, 88 | {"[%LEVEL][%LEVEL]", "", WarnLvl, "[WARN][WARN]", false}, 89 | {"[%LEVEL]X[%Level]", "", ErrorLvl, "[ERROR]X[Error]", false}, 90 | {"%LEVELLL", "", CriticalLvl, "CRITICALLL", false}, 91 | {"%LEV", "", TraceLvl, "TRC", false}, 92 | {"%LEV", "", DebugLvl, "DBG", false}, 93 | {"%LEV", "", InfoLvl, "INF", false}, 94 | {"%LEV", "", WarnLvl, "WRN", false}, 95 | {"%LEV", "", ErrorLvl, "ERR", false}, 96 | {"%LEV", "", CriticalLvl, "CRT", false}, 97 | {"[%LEV]", "", TraceLvl, "[TRC]", false}, 98 | {"[%LEV]", "abc", DebugLvl, "[DBG]", false}, 99 | {"%LEVLEVEL", "", InfoLvl, "INFLEVEL", false}, 100 | {"[%LEVEL][%LEV]", "", WarnLvl, "[WARN][WRN]", false}, 101 | {"[%LEV]X[%LEV]", "", ErrorLvl, "[ERR]X[ERR]", false}, 102 | {"%LEVLL", "", CriticalLvl, "CRTLL", false}, 103 | {"%l", "", TraceLvl, "t", false}, 104 | {"%l", "", DebugLvl, "d", false}, 105 | {"%l", "", InfoLvl, "i", false}, 106 | {"%l", "", WarnLvl, "w", false}, 107 | {"%l", "", ErrorLvl, "e", false}, 108 | {"%l", "", CriticalLvl, "c", false}, 109 | {"[%l]", "", TraceLvl, "[t]", false}, 110 | {"[%l]", "abc", DebugLvl, "[d]", false}, 111 | {"%Level%Msg", "", TraceLvl, "Trace", false}, 112 | {"%Level%Msg", "A", DebugLvl, "DebugA", false}, 113 | {"%Level%Msg", "", InfoLvl, "Info", false}, 114 | {"%Level%Msg", "test", WarnLvl, "Warntest", false}, 115 | {"%Level%Msg", " ", ErrorLvl, "Error ", false}, 116 | {"%Level%Msg", "", CriticalLvl, "Critical", false}, 117 | {"[%Level]", "", TraceLvl, "[Trace]", false}, 118 | {"[%Level]", "abc", DebugLvl, "[Debug]", false}, 119 | {"%Level%MsgLevel", "A", InfoLvl, "InfoALevel", false}, 120 | {"[%Level]%Msg[%Level]", "test", WarnLvl, "[Warn]test[Warn]", false}, 121 | {"[%Level]%MsgX[%Level]", "test", ErrorLvl, "[Error]testX[Error]", false}, 122 | {"%Levell%Msgl", "Test", CriticalLvl, "CriticallTestl", false}, 123 | {"%Lev%Msg%LEVEL%LEV%l%Msg", "Test", InfoLvl, "InfTestINFOINFiTest", false}, 124 | {"%r", "", CriticalLvl, "\r", false}, 125 | {"%n", "", CriticalLvl, "\n", false}, 126 | {"%t", "", CriticalLvl, "\t", false}, 127 | } 128 | 129 | func TestFormats(t *testing.T) { 130 | 131 | context, conErr := currentContext(nil) 132 | if conErr != nil { 133 | t.Fatal("Cannot get current context:" + conErr.Error()) 134 | return 135 | } 136 | 137 | for _, test := range formatTests { 138 | 139 | form, err := NewFormatter(test.formatString) 140 | 141 | if (err != nil) != test.errorExpected { 142 | t.Errorf("input: %s \nInput LL: %s\n* Expected error:%t Got error: %t\n", 143 | test.input, test.inputLogLevel, test.errorExpected, (err != nil)) 144 | if err != nil { 145 | t.Logf("%s\n", err.Error()) 146 | } 147 | continue 148 | } else if err != nil { 149 | continue 150 | } 151 | 152 | msg := form.Format(test.input, test.inputLogLevel, context) 153 | 154 | if err == nil && msg != test.expectedOutput { 155 | t.Errorf("format: %s \nInput: %s \nInput LL: %s\n* Expected: %s \n* Got: %s\n", 156 | test.formatString, test.input, test.inputLogLevel, test.expectedOutput, msg) 157 | } 158 | } 159 | } 160 | 161 | func TestDateFormat(t *testing.T) { 162 | _, err := NewFormatter("%Date") 163 | if err != nil { 164 | t.Error("Unexpected error: " + err.Error()) 165 | } 166 | } 167 | 168 | func TestDateParameterizedFormat(t *testing.T) { 169 | testFormat := "Mon Jan 02 2006 15:04:05" 170 | preciseForamt := "Mon Jan 02 2006 15:04:05.000" 171 | 172 | context, conErr := currentContext(nil) 173 | if conErr != nil { 174 | t.Fatal("Cannot get current context:" + conErr.Error()) 175 | return 176 | } 177 | 178 | form, err := NewFormatter("%Date(" + preciseForamt + ")") 179 | if err != nil { 180 | t.Error("Unexpected error: " + err.Error()) 181 | } 182 | 183 | dateBefore := time.Now().Format(testFormat) 184 | msg := form.Format("", TraceLvl, context) 185 | dateAfter := time.Now().Format(testFormat) 186 | 187 | if !strings.HasPrefix(msg, dateBefore) && !strings.HasPrefix(msg, dateAfter) { 188 | t.Errorf("incorrect message: %v. Expected %v or %v", msg, dateBefore, dateAfter) 189 | } 190 | 191 | _, err = NewFormatter("%Date(" + preciseForamt) 192 | if err == nil { 193 | t.Error("Expected error for invalid format") 194 | } 195 | } 196 | 197 | func createTestFormatter(format string) FormatterFunc { 198 | return func(message string, level LogLevel, context LogContextInterface) interface{} { 199 | return "TEST " + context.Func() + " TEST" 200 | } 201 | } 202 | 203 | func TestCustomFormatterRegistration(t *testing.T) { 204 | err := RegisterCustomFormatter("Level", createTestFormatter) 205 | if err == nil { 206 | t.Errorf("expected an error when trying to register a custom formatter with a reserved alias") 207 | } 208 | err = RegisterCustomFormatter("EscM", createTestFormatter) 209 | if err == nil { 210 | t.Errorf("expected an error when trying to register a custom formatter with a reserved parameterized alias") 211 | } 212 | err = RegisterCustomFormatter("TEST", createTestFormatter) 213 | if err != nil { 214 | t.Fatalf("Registering custom formatter: unexpected error: %s", err) 215 | } 216 | err = RegisterCustomFormatter("TEST", createTestFormatter) 217 | if err == nil { 218 | t.Errorf("expected an error when trying to register a custom formatter with duplicate name") 219 | } 220 | 221 | context, conErr := currentContext(nil) 222 | if conErr != nil { 223 | t.Fatal("Cannot get current context:" + conErr.Error()) 224 | return 225 | } 226 | 227 | form, err := NewFormatter("%Msg %TEST 123") 228 | if err != nil { 229 | t.Fatalf("%s\n", err.Error()) 230 | } 231 | 232 | expected := fmt.Sprintf("test TEST %sTestCustomFormatterRegistration TEST 123", commonPrefix) 233 | msg := form.Format("test", DebugLvl, context) 234 | if msg != expected { 235 | t.Fatalf("Custom formatter: invalid output. Expected: '%s'. Got: '%s'", expected, msg) 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /internals_baseerror.go: -------------------------------------------------------------------------------- 1 | package seelog 2 | 3 | // Base struct for custom errors. 4 | type baseError struct { 5 | message string 6 | } 7 | 8 | func (be baseError) Error() string { 9 | return be.message 10 | } 11 | -------------------------------------------------------------------------------- /internals_byteverifiers_test.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "errors" 29 | "strconv" 30 | "testing" 31 | ) 32 | 33 | // bytesVerifier is a byte receiver which is used for correct input testing. 34 | // It allows to compare expected result and actual result in context of received bytes. 35 | type bytesVerifier struct { 36 | expectedBytes []byte // bytes that are expected to be written in next Write call 37 | waitingForInput bool // true if verifier is waiting for a Write call 38 | writtenData []byte // real bytes that actually were received during the last Write call 39 | testEnv *testing.T 40 | } 41 | 42 | func newBytesVerifier(t *testing.T) (*bytesVerifier, error) { 43 | if t == nil { 44 | return nil, errors.New("testing environment param is nil") 45 | } 46 | 47 | verifier := new(bytesVerifier) 48 | verifier.testEnv = t 49 | 50 | return verifier, nil 51 | } 52 | 53 | // Write is used to check whether verifier was waiting for input and whether bytes are the same as expectedBytes. 54 | // After Write call, waitingForInput is set to false. 55 | func (verifier *bytesVerifier) Write(bytes []byte) (n int, err error) { 56 | if !verifier.waitingForInput { 57 | verifier.testEnv.Errorf("unexpected input: %v", string(bytes)) 58 | return 59 | } 60 | 61 | verifier.waitingForInput = false 62 | verifier.writtenData = bytes 63 | 64 | if verifier.expectedBytes != nil { 65 | if bytes == nil { 66 | verifier.testEnv.Errorf("incoming 'bytes' is nil") 67 | } else { 68 | if len(bytes) != len(verifier.expectedBytes) { 69 | verifier.testEnv.Errorf("'Bytes' has unexpected len. Expected: %d. Got: %d. . Expected string: %q. Got: %q", 70 | len(verifier.expectedBytes), len(bytes), string(verifier.expectedBytes), string(bytes)) 71 | } else { 72 | for i := 0; i < len(bytes); i++ { 73 | if verifier.expectedBytes[i] != bytes[i] { 74 | verifier.testEnv.Errorf("incorrect data on position %d. Expected: %d. Got: %d. Expected string: %q. Got: %q", 75 | i, verifier.expectedBytes[i], bytes[i], string(verifier.expectedBytes), string(bytes)) 76 | break 77 | } 78 | } 79 | } 80 | } 81 | } 82 | 83 | return len(bytes), nil 84 | } 85 | 86 | func (verifier *bytesVerifier) ExpectBytes(bytes []byte) { 87 | verifier.waitingForInput = true 88 | verifier.expectedBytes = bytes 89 | } 90 | 91 | func (verifier *bytesVerifier) MustNotExpect() { 92 | if verifier.waitingForInput { 93 | errorText := "Unexpected input: " 94 | 95 | if verifier.expectedBytes != nil { 96 | errorText += "len = " + strconv.Itoa(len(verifier.expectedBytes)) 97 | errorText += ". text = " + string(verifier.expectedBytes) 98 | } 99 | 100 | verifier.testEnv.Errorf(errorText) 101 | } 102 | } 103 | 104 | func (verifier *bytesVerifier) Close() error { 105 | return nil 106 | } 107 | 108 | // nullWriter implements io.Writer inteface and does nothing, always returning a successful write result 109 | type nullWriter struct { 110 | } 111 | 112 | func (writer *nullWriter) Write(bytes []byte) (n int, err error) { 113 | return len(bytes), nil 114 | } 115 | 116 | func (writer *nullWriter) Close() error { 117 | return nil 118 | } 119 | -------------------------------------------------------------------------------- /internals_xmlnode.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "encoding/xml" 29 | "errors" 30 | "fmt" 31 | "io" 32 | "strings" 33 | ) 34 | 35 | type xmlNode struct { 36 | name string 37 | attributes map[string]string 38 | children []*xmlNode 39 | value string 40 | } 41 | 42 | func newNode() *xmlNode { 43 | node := new(xmlNode) 44 | node.children = make([]*xmlNode, 0) 45 | node.attributes = make(map[string]string) 46 | return node 47 | } 48 | 49 | func (node *xmlNode) String() string { 50 | str := fmt.Sprintf("<%s", node.name) 51 | 52 | for attrName, attrVal := range node.attributes { 53 | str += fmt.Sprintf(" %s=\"%s\"", attrName, attrVal) 54 | } 55 | 56 | str += ">" 57 | str += node.value 58 | 59 | if len(node.children) != 0 { 60 | for _, child := range node.children { 61 | str += fmt.Sprintf("%s", child) 62 | } 63 | } 64 | 65 | str += fmt.Sprintf("", node.name) 66 | 67 | return str 68 | } 69 | 70 | func (node *xmlNode) unmarshal(startEl xml.StartElement) error { 71 | node.name = startEl.Name.Local 72 | 73 | for _, v := range startEl.Attr { 74 | _, alreadyExists := node.attributes[v.Name.Local] 75 | if alreadyExists { 76 | return errors.New("tag '" + node.name + "' has duplicated attribute: '" + v.Name.Local + "'") 77 | } 78 | node.attributes[v.Name.Local] = v.Value 79 | } 80 | 81 | return nil 82 | } 83 | 84 | func (node *xmlNode) add(child *xmlNode) { 85 | if node.children == nil { 86 | node.children = make([]*xmlNode, 0) 87 | } 88 | 89 | node.children = append(node.children, child) 90 | } 91 | 92 | func (node *xmlNode) hasChildren() bool { 93 | return node.children != nil && len(node.children) > 0 94 | } 95 | 96 | //============================================= 97 | 98 | func unmarshalConfig(reader io.Reader) (*xmlNode, error) { 99 | xmlParser := xml.NewDecoder(reader) 100 | 101 | config, err := unmarshalNode(xmlParser, nil) 102 | if err != nil { 103 | return nil, err 104 | } 105 | if config == nil { 106 | return nil, errors.New("xml has no content") 107 | } 108 | 109 | nextConfigEntry, err := unmarshalNode(xmlParser, nil) 110 | if nextConfigEntry != nil { 111 | return nil, errors.New("xml contains more than one root element") 112 | } 113 | 114 | return config, nil 115 | } 116 | 117 | func unmarshalNode(xmlParser *xml.Decoder, curToken xml.Token) (node *xmlNode, err error) { 118 | firstLoop := true 119 | for { 120 | var tok xml.Token 121 | if firstLoop && curToken != nil { 122 | tok = curToken 123 | firstLoop = false 124 | } else { 125 | tok, err = getNextToken(xmlParser) 126 | if err != nil || tok == nil { 127 | return 128 | } 129 | } 130 | 131 | switch tt := tok.(type) { 132 | case xml.SyntaxError: 133 | err = errors.New(tt.Error()) 134 | return 135 | case xml.CharData: 136 | value := strings.TrimSpace(string([]byte(tt))) 137 | if node != nil { 138 | node.value += value 139 | } 140 | case xml.StartElement: 141 | if node == nil { 142 | node = newNode() 143 | err := node.unmarshal(tt) 144 | if err != nil { 145 | return nil, err 146 | } 147 | } else { 148 | childNode, childErr := unmarshalNode(xmlParser, tok) 149 | if childErr != nil { 150 | return nil, childErr 151 | } 152 | 153 | if childNode != nil { 154 | node.add(childNode) 155 | } else { 156 | return 157 | } 158 | } 159 | case xml.EndElement: 160 | return 161 | } 162 | } 163 | } 164 | 165 | func getNextToken(xmlParser *xml.Decoder) (tok xml.Token, err error) { 166 | if tok, err = xmlParser.Token(); err != nil { 167 | if err == io.EOF { 168 | err = nil 169 | return 170 | } 171 | return 172 | } 173 | 174 | return 175 | } 176 | -------------------------------------------------------------------------------- /internals_xmlnode_test.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "strings" 29 | "testing" 30 | //"fmt" 31 | "reflect" 32 | ) 33 | 34 | var testEnv *testing.T 35 | 36 | /*func TestWrapper(t *testing.T) { 37 | testEnv = t 38 | 39 | s := "" 40 | reader := strings.NewReader(s) 41 | config, err := unmarshalConfig(reader) 42 | if err != nil { 43 | testEnv.Error(err) 44 | return 45 | } 46 | 47 | printXML(config, 0) 48 | } 49 | 50 | func printXML(node *xmlNode, level int) { 51 | indent := strings.Repeat("\t", level) 52 | fmt.Print(indent + node.name) 53 | for key, value := range node.attributes { 54 | fmt.Print(" " + key + "/" + value) 55 | } 56 | fmt.Println() 57 | 58 | for _, child := range node.children { 59 | printXML(child, level+1) 60 | } 61 | }*/ 62 | 63 | var xmlNodeTests []xmlNodeTest 64 | 65 | type xmlNodeTest struct { 66 | testName string 67 | inputXML string 68 | expected interface{} 69 | errorExpected bool 70 | } 71 | 72 | func getXMLTests() []xmlNodeTest { 73 | if xmlNodeTests == nil { 74 | xmlNodeTests = make([]xmlNodeTest, 0) 75 | 76 | testName := "Simple test" 77 | testXML := `` 78 | testExpected := newNode() 79 | testExpected.name = "a" 80 | xmlNodeTests = append(xmlNodeTests, xmlNodeTest{testName, testXML, testExpected, false}) 81 | 82 | testName = "Multiline test" 83 | testXML = 84 | ` 85 | 86 | 87 | ` 88 | testExpected = newNode() 89 | testExpected.name = "a" 90 | xmlNodeTests = append(xmlNodeTests, xmlNodeTest{testName, testXML, testExpected, false}) 91 | 92 | testName = "Multiline test #2" 93 | testXML = 94 | ` 95 | 96 | 97 | 98 | 99 | 100 | 101 | ` 102 | testExpected = newNode() 103 | testExpected.name = "a" 104 | xmlNodeTests = append(xmlNodeTests, xmlNodeTest{testName, testXML, testExpected, false}) 105 | 106 | testName = "Incorrect names" 107 | testXML = `< a >< /a >` 108 | xmlNodeTests = append(xmlNodeTests, xmlNodeTest{testName, testXML, nil, true}) 109 | 110 | testName = "Comments" 111 | testXML = 112 | ` 113 | 114 | 115 | ` 116 | testExpected = newNode() 117 | testExpected.name = "a" 118 | xmlNodeTests = append(xmlNodeTests, xmlNodeTest{testName, testXML, testExpected, false}) 119 | 120 | testName = "Multiple roots" 121 | testXML = `` 122 | xmlNodeTests = append(xmlNodeTests, xmlNodeTest{testName, testXML, nil, true}) 123 | 124 | testName = "Multiple roots + incorrect xml" 125 | testXML = `` 126 | xmlNodeTests = append(xmlNodeTests, xmlNodeTest{testName, testXML, nil, true}) 127 | 128 | testName = "Some unicode and data" 129 | testXML = `<俄语>данные` 130 | testExpected = newNode() 131 | testExpected.name = "俄语" 132 | testExpected.value = "данные" 133 | xmlNodeTests = append(xmlNodeTests, xmlNodeTest{testName, testXML, testExpected, false}) 134 | 135 | testName = "Values and children" 136 | testXML = `<俄语>данные` 137 | testExpected = newNode() 138 | testExpected.name = "俄语" 139 | testExpected.value = "данные" 140 | child := newNode() 141 | child.name = "and_a_child" 142 | testExpected.children = append(testExpected.children, child) 143 | xmlNodeTests = append(xmlNodeTests, xmlNodeTest{testName, testXML, testExpected, false}) 144 | 145 | testName = "Just children" 146 | testXML = `<俄语>` 147 | testExpected = newNode() 148 | testExpected.name = "俄语" 149 | child = newNode() 150 | child.name = "and_a_child" 151 | testExpected.children = append(testExpected.children, child) 152 | xmlNodeTests = append(xmlNodeTests, xmlNodeTest{testName, testXML, testExpected, false}) 153 | 154 | testName = "Mixed test" 155 | testXML = `<俄语 a="1" b="2.13" c="abc">` 156 | testExpected = newNode() 157 | testExpected.name = "俄语" 158 | testExpected.attributes["a"] = "1" 159 | testExpected.attributes["b"] = "2.13" 160 | testExpected.attributes["c"] = "abc" 161 | child = newNode() 162 | child.name = "child" 163 | child.attributes["abc"] = "bca" 164 | testExpected.children = append(testExpected.children, child) 165 | child = newNode() 166 | child.name = "child" 167 | child.attributes["abc"] = "def" 168 | testExpected.children = append(testExpected.children, child) 169 | xmlNodeTests = append(xmlNodeTests, xmlNodeTest{testName, testXML, testExpected, false}) 170 | } 171 | 172 | return xmlNodeTests 173 | } 174 | 175 | func TestXmlNode(t *testing.T) { 176 | 177 | for _, test := range getXMLTests() { 178 | 179 | reader := strings.NewReader(test.inputXML) 180 | parsedXML, err := unmarshalConfig(reader) 181 | 182 | if (err != nil) != test.errorExpected { 183 | t.Errorf("\n%s:\nXML input: %s\nExpected error:%t. Got error: %t\n", test.testName, 184 | test.inputXML, test.errorExpected, (err != nil)) 185 | if err != nil { 186 | t.Logf("%s\n", err.Error()) 187 | } 188 | continue 189 | } 190 | 191 | if err == nil && !reflect.DeepEqual(parsedXML, test.expected) { 192 | t.Errorf("\n%s:\nXML input: %s\nExpected: %s. \nGot: %s\n", test.testName, 193 | test.inputXML, test.expected, parsedXML) 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /io/iotest/iotest.go: -------------------------------------------------------------------------------- 1 | package iotest 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "io/ioutil" 7 | "os" 8 | "testing" 9 | ) 10 | 11 | // TempFile creates a new temporary file for testing and returns the file 12 | // pointer and a cleanup function for closing and removing the file. 13 | func TempFile(t *testing.T) (*os.File, func()) { 14 | f, err := ioutil.TempFile("", "test") 15 | if err != nil { 16 | t.Fatal(err) 17 | } 18 | return f, func() { 19 | os.Remove(f.Name()) 20 | f.Close() 21 | } 22 | } 23 | 24 | // FileInfo computes the os.FileInfo for a given byte slice. 25 | func FileInfo(t *testing.T, fbytes []byte) os.FileInfo { 26 | // Get FileInfo 27 | f, clean := TempFile(t) 28 | defer clean() 29 | _, err := io.Copy(f, bytes.NewReader(fbytes)) 30 | if err != nil { 31 | t.Fatalf("copy to temp file: %v", err) 32 | } 33 | fi, err := f.Stat() 34 | if err != nil { 35 | t.Fatalf("stat temp file: %v", err) 36 | } 37 | return fi 38 | } 39 | -------------------------------------------------------------------------------- /io/iotest/iotest_test.go: -------------------------------------------------------------------------------- 1 | package iotest 2 | 3 | import ( 4 | "os" 5 | "syscall" 6 | "testing" 7 | ) 8 | 9 | func TestTempFile(t *testing.T) { 10 | f, cleanup := TempFile(t) 11 | if _, err := f.Write([]byte("test")); err != nil { 12 | t.Fatalf("temp file not writable: %v", err) 13 | } 14 | cleanup() 15 | // Confirm closed 16 | 17 | if err := f.Close(); err != syscall.EINVAL { 18 | t.Errorf("temp file was not closed by cleanup func") 19 | } 20 | if _, err := os.Stat(f.Name()); !os.IsNotExist(err) { 21 | t.Errorf("temp file was not removed by cleanup func") 22 | } 23 | } 24 | 25 | var finfoTests = map[string]string{ 26 | "empty": "", 27 | "non-empty": "I am a log file", 28 | } 29 | 30 | func TestFileInfo(t *testing.T) { 31 | for name, in := range finfoTests { 32 | got := FileInfo(t, []byte(in)) 33 | testEqual(t, name, "size", got.Size(), int64(len(in))) 34 | testEqual(t, name, "mode", got.Mode(), os.FileMode(0600)) 35 | testEqual(t, name, "isDir", got.IsDir(), false) 36 | } 37 | } 38 | 39 | func testEqual(t *testing.T, name, field string, got, want interface{}) { 40 | if got != want { 41 | t.Errorf("%s: incorrect %v: got %q but want %q", name, field, got, want) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /writers_bufferedwriter.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "bufio" 29 | "errors" 30 | "fmt" 31 | "io" 32 | "sync" 33 | "time" 34 | ) 35 | 36 | // bufferedWriter stores data in memory and flushes it every flushPeriod or when buffer is full 37 | type bufferedWriter struct { 38 | flushPeriod time.Duration // data flushes interval (in microseconds) 39 | bufferMutex *sync.Mutex // mutex for buffer operations syncronization 40 | innerWriter io.Writer // inner writer 41 | buffer *bufio.Writer // buffered wrapper for inner writer 42 | bufferSize int // max size of data chunk in bytes 43 | } 44 | 45 | // NewBufferedWriter creates a new buffered writer struct. 46 | // bufferSize -- size of memory buffer in bytes 47 | // flushPeriod -- period in which data flushes from memory buffer in milliseconds. 0 - turn off this functionality 48 | func NewBufferedWriter(innerWriter io.Writer, bufferSize int, flushPeriod time.Duration) (*bufferedWriter, error) { 49 | 50 | if innerWriter == nil { 51 | return nil, errors.New("argument is nil: innerWriter") 52 | } 53 | if flushPeriod < 0 { 54 | return nil, fmt.Errorf("flushPeriod can not be less than 0. Got: %d", flushPeriod) 55 | } 56 | 57 | if bufferSize <= 0 { 58 | return nil, fmt.Errorf("bufferSize can not be less or equal to 0. Got: %d", bufferSize) 59 | } 60 | 61 | buffer := bufio.NewWriterSize(innerWriter, bufferSize) 62 | 63 | /*if err != nil { 64 | return nil, err 65 | }*/ 66 | 67 | newWriter := new(bufferedWriter) 68 | 69 | newWriter.innerWriter = innerWriter 70 | newWriter.buffer = buffer 71 | newWriter.bufferSize = bufferSize 72 | newWriter.flushPeriod = flushPeriod * 1e6 73 | newWriter.bufferMutex = new(sync.Mutex) 74 | 75 | if flushPeriod != 0 { 76 | go newWriter.flushPeriodically() 77 | } 78 | 79 | return newWriter, nil 80 | } 81 | 82 | func (bufWriter *bufferedWriter) writeBigChunk(bytes []byte) (n int, err error) { 83 | bufferedLen := bufWriter.buffer.Buffered() 84 | 85 | n, err = bufWriter.flushInner() 86 | if err != nil { 87 | return 88 | } 89 | 90 | written, writeErr := bufWriter.innerWriter.Write(bytes) 91 | return bufferedLen + written, writeErr 92 | } 93 | 94 | // Sends data to buffer manager. Waits until all buffers are full. 95 | func (bufWriter *bufferedWriter) Write(bytes []byte) (n int, err error) { 96 | 97 | bufWriter.bufferMutex.Lock() 98 | defer bufWriter.bufferMutex.Unlock() 99 | 100 | bytesLen := len(bytes) 101 | 102 | if bytesLen > bufWriter.bufferSize { 103 | return bufWriter.writeBigChunk(bytes) 104 | } 105 | 106 | if bytesLen > bufWriter.buffer.Available() { 107 | n, err = bufWriter.flushInner() 108 | if err != nil { 109 | return 110 | } 111 | } 112 | 113 | bufWriter.buffer.Write(bytes) 114 | 115 | return len(bytes), nil 116 | } 117 | 118 | func (bufWriter *bufferedWriter) Close() error { 119 | closer, ok := bufWriter.innerWriter.(io.Closer) 120 | if ok { 121 | return closer.Close() 122 | } 123 | 124 | return nil 125 | } 126 | 127 | func (bufWriter *bufferedWriter) Flush() { 128 | 129 | bufWriter.bufferMutex.Lock() 130 | defer bufWriter.bufferMutex.Unlock() 131 | 132 | bufWriter.flushInner() 133 | } 134 | 135 | func (bufWriter *bufferedWriter) flushInner() (n int, err error) { 136 | bufferedLen := bufWriter.buffer.Buffered() 137 | flushErr := bufWriter.buffer.Flush() 138 | 139 | return bufWriter.buffer.Buffered() - bufferedLen, flushErr 140 | } 141 | 142 | func (bufWriter *bufferedWriter) flushBuffer() { 143 | bufWriter.bufferMutex.Lock() 144 | defer bufWriter.bufferMutex.Unlock() 145 | 146 | bufWriter.buffer.Flush() 147 | } 148 | 149 | func (bufWriter *bufferedWriter) flushPeriodically() { 150 | if bufWriter.flushPeriod > 0 { 151 | ticker := time.NewTicker(bufWriter.flushPeriod) 152 | for { 153 | <-ticker.C 154 | bufWriter.flushBuffer() 155 | } 156 | } 157 | } 158 | 159 | func (bufWriter *bufferedWriter) String() string { 160 | return fmt.Sprintf("bufferedWriter size: %d, flushPeriod: %d", bufWriter.bufferSize, bufWriter.flushPeriod) 161 | } 162 | -------------------------------------------------------------------------------- /writers_bufferedwriter_test.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "testing" 29 | ) 30 | 31 | func TestChunkWriteOnFilling(t *testing.T) { 32 | writer, _ := newBytesVerifier(t) 33 | bufferedWriter, err := NewBufferedWriter(writer, 1024, 0) 34 | 35 | if err != nil { 36 | t.Fatalf("Unexpected buffered writer creation error: %s", err.Error()) 37 | } 38 | 39 | bytes := make([]byte, 1000) 40 | 41 | bufferedWriter.Write(bytes) 42 | writer.ExpectBytes(bytes) 43 | bufferedWriter.Write(bytes) 44 | } 45 | 46 | func TestBigMessageMustPassMemoryBuffer(t *testing.T) { 47 | writer, _ := newBytesVerifier(t) 48 | bufferedWriter, err := NewBufferedWriter(writer, 1024, 0) 49 | 50 | if err != nil { 51 | t.Fatalf("Unexpected buffered writer creation error: %s", err.Error()) 52 | } 53 | 54 | bytes := make([]byte, 5000) 55 | 56 | for i := 0; i < len(bytes); i++ { 57 | bytes[i] = uint8(i % 255) 58 | } 59 | 60 | writer.ExpectBytes(bytes) 61 | bufferedWriter.Write(bytes) 62 | } 63 | -------------------------------------------------------------------------------- /writers_connwriter.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "crypto/tls" 29 | "fmt" 30 | "io" 31 | "net" 32 | ) 33 | 34 | // connWriter is used to write to a stream-oriented network connection. 35 | type connWriter struct { 36 | innerWriter io.WriteCloser 37 | reconnectOnMsg bool 38 | reconnect bool 39 | net string 40 | addr string 41 | useTLS bool 42 | configTLS *tls.Config 43 | } 44 | 45 | // Creates writer to the address addr on the network netName. 46 | // Connection will be opened on each write if reconnectOnMsg = true 47 | func NewConnWriter(netName string, addr string, reconnectOnMsg bool) *connWriter { 48 | newWriter := new(connWriter) 49 | 50 | newWriter.net = netName 51 | newWriter.addr = addr 52 | newWriter.reconnectOnMsg = reconnectOnMsg 53 | 54 | return newWriter 55 | } 56 | 57 | // Creates a writer that uses SSL/TLS 58 | func newTLSWriter(netName string, addr string, reconnectOnMsg bool, config *tls.Config) *connWriter { 59 | newWriter := new(connWriter) 60 | 61 | newWriter.net = netName 62 | newWriter.addr = addr 63 | newWriter.reconnectOnMsg = reconnectOnMsg 64 | newWriter.useTLS = true 65 | newWriter.configTLS = config 66 | 67 | return newWriter 68 | } 69 | 70 | func (connWriter *connWriter) Close() error { 71 | if connWriter.innerWriter == nil { 72 | return nil 73 | } 74 | 75 | return connWriter.innerWriter.Close() 76 | } 77 | 78 | func (connWriter *connWriter) Write(bytes []byte) (n int, err error) { 79 | if connWriter.neededConnectOnMsg() { 80 | err = connWriter.connect() 81 | if err != nil { 82 | return 0, err 83 | } 84 | } 85 | 86 | if connWriter.reconnectOnMsg { 87 | defer connWriter.innerWriter.Close() 88 | } 89 | 90 | n, err = connWriter.innerWriter.Write(bytes) 91 | if err != nil { 92 | connWriter.reconnect = true 93 | } 94 | 95 | return 96 | } 97 | 98 | func (connWriter *connWriter) String() string { 99 | return fmt.Sprintf("Conn writer: [%s, %s, %v]", connWriter.net, connWriter.addr, connWriter.reconnectOnMsg) 100 | } 101 | 102 | func (connWriter *connWriter) connect() error { 103 | if connWriter.innerWriter != nil { 104 | connWriter.innerWriter.Close() 105 | connWriter.innerWriter = nil 106 | } 107 | 108 | if connWriter.useTLS { 109 | conn, err := tls.Dial(connWriter.net, connWriter.addr, connWriter.configTLS) 110 | if err != nil { 111 | return err 112 | } 113 | connWriter.innerWriter = conn 114 | 115 | return nil 116 | } 117 | 118 | conn, err := net.Dial(connWriter.net, connWriter.addr) 119 | if err != nil { 120 | return err 121 | } 122 | 123 | tcpConn, ok := conn.(*net.TCPConn) 124 | if ok { 125 | tcpConn.SetKeepAlive(true) 126 | } 127 | 128 | connWriter.innerWriter = conn 129 | 130 | return nil 131 | } 132 | 133 | func (connWriter *connWriter) neededConnectOnMsg() bool { 134 | if connWriter.reconnect { 135 | connWriter.reconnect = false 136 | return true 137 | } 138 | 139 | if connWriter.innerWriter == nil { 140 | return true 141 | } 142 | 143 | return connWriter.reconnectOnMsg 144 | } 145 | -------------------------------------------------------------------------------- /writers_consolewriter.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import "fmt" 28 | 29 | // consoleWriter is used to write to console 30 | type consoleWriter struct { 31 | } 32 | 33 | // Creates a new console writer. Returns error, if the console writer couldn't be created. 34 | func NewConsoleWriter() (writer *consoleWriter, err error) { 35 | newWriter := new(consoleWriter) 36 | 37 | return newWriter, nil 38 | } 39 | 40 | // Create folder and file on WriteLog/Write first call 41 | func (console *consoleWriter) Write(bytes []byte) (int, error) { 42 | return fmt.Print(string(bytes)) 43 | } 44 | 45 | func (console *consoleWriter) String() string { 46 | return "Console writer" 47 | } 48 | -------------------------------------------------------------------------------- /writers_filewriter.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "fmt" 29 | "io" 30 | "os" 31 | "path/filepath" 32 | ) 33 | 34 | // fileWriter is used to write to a file. 35 | type fileWriter struct { 36 | innerWriter io.WriteCloser 37 | fileName string 38 | } 39 | 40 | // Creates a new file and a corresponding writer. Returns error, if the file couldn't be created. 41 | func NewFileWriter(fileName string) (writer *fileWriter, err error) { 42 | newWriter := new(fileWriter) 43 | newWriter.fileName = fileName 44 | 45 | return newWriter, nil 46 | } 47 | 48 | func (fw *fileWriter) Close() error { 49 | if fw.innerWriter != nil { 50 | err := fw.innerWriter.Close() 51 | if err != nil { 52 | return err 53 | } 54 | fw.innerWriter = nil 55 | } 56 | return nil 57 | } 58 | 59 | // Create folder and file on WriteLog/Write first call 60 | func (fw *fileWriter) Write(bytes []byte) (n int, err error) { 61 | if fw.innerWriter == nil { 62 | if err := fw.createFile(); err != nil { 63 | return 0, err 64 | } 65 | } 66 | return fw.innerWriter.Write(bytes) 67 | } 68 | 69 | func (fw *fileWriter) createFile() error { 70 | folder, _ := filepath.Split(fw.fileName) 71 | var err error 72 | 73 | if 0 != len(folder) { 74 | err = os.MkdirAll(folder, defaultDirectoryPermissions) 75 | if err != nil { 76 | return err 77 | } 78 | } 79 | 80 | // If exists 81 | fw.innerWriter, err = os.OpenFile(fw.fileName, os.O_WRONLY|os.O_APPEND|os.O_CREATE, defaultFilePermissions) 82 | 83 | if err != nil { 84 | return err 85 | } 86 | 87 | return nil 88 | } 89 | 90 | func (fw *fileWriter) String() string { 91 | return fmt.Sprintf("File writer: %s", fw.fileName) 92 | } 93 | -------------------------------------------------------------------------------- /writers_filewriter_test.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "fmt" 29 | "io" 30 | "os" 31 | "path/filepath" 32 | "strings" 33 | "testing" 34 | ) 35 | 36 | const ( 37 | messageLen = 10 38 | ) 39 | 40 | var bytesFileTest = []byte(strings.Repeat("A", messageLen)) 41 | 42 | func TestSimpleFileWriter(t *testing.T) { 43 | t.Logf("Starting file writer tests") 44 | NewFileWriterTester(simplefileWriterTests, simplefileWriterGetter, t).test() 45 | } 46 | 47 | //=============================================================== 48 | 49 | func simplefileWriterGetter(testCase *fileWriterTestCase) (io.WriteCloser, error) { 50 | return NewFileWriter(testCase.fileName) 51 | } 52 | 53 | //=============================================================== 54 | type fileWriterTestCase struct { 55 | files []string 56 | fileName string 57 | rollingType rollingType 58 | fileSize int64 59 | maxRolls int 60 | datePattern string 61 | writeCount int 62 | resFiles []string 63 | nameMode rollingNameMode 64 | archiveType rollingArchiveType 65 | archiveExploded bool 66 | archivePath string 67 | } 68 | 69 | func createSimplefileWriterTestCase(fileName string, writeCount int) *fileWriterTestCase { 70 | return &fileWriterTestCase{[]string{}, fileName, rollingTypeSize, 0, 0, "", writeCount, []string{fileName}, 0, rollingArchiveNone, false, ""} 71 | } 72 | 73 | var simplefileWriterTests = []*fileWriterTestCase{ 74 | createSimplefileWriterTestCase("log.testlog", 1), 75 | createSimplefileWriterTestCase("log.testlog", 50), 76 | createSimplefileWriterTestCase(filepath.Join("dir", "log.testlog"), 50), 77 | } 78 | 79 | //=============================================================== 80 | 81 | type fileWriterTester struct { 82 | testCases []*fileWriterTestCase 83 | writerGetter func(*fileWriterTestCase) (io.WriteCloser, error) 84 | t *testing.T 85 | } 86 | 87 | func NewFileWriterTester( 88 | testCases []*fileWriterTestCase, 89 | writerGetter func(*fileWriterTestCase) (io.WriteCloser, error), 90 | t *testing.T) *fileWriterTester { 91 | 92 | return &fileWriterTester{testCases, writerGetter, t} 93 | } 94 | 95 | func isWriterTestFile(fn string) bool { 96 | return strings.Contains(fn, ".testlog") || strings.Contains(fn, ".zip") || strings.Contains(fn, ".gz") 97 | } 98 | 99 | func cleanupWriterTest(t *testing.T) { 100 | toDel, err := getDirFilePaths(".", isWriterTestFile, true) 101 | if nil != err { 102 | t.Fatal("Cannot list files in test directory!") 103 | } 104 | 105 | for _, p := range toDel { 106 | if err = tryRemoveFile(p); nil != err { 107 | t.Errorf("cannot remove file %s in test directory: %s", p, err.Error()) 108 | } 109 | } 110 | 111 | if err = os.RemoveAll("dir"); nil != err { 112 | t.Errorf("cannot remove temp test directory: %s", err.Error()) 113 | } 114 | } 115 | 116 | func getWriterTestResultFiles() ([]string, error) { 117 | var p []string 118 | 119 | visit := func(path string, f os.FileInfo, err error) error { 120 | if !f.IsDir() && isWriterTestFile(path) { 121 | abs, err := filepath.Abs(path) 122 | if err != nil { 123 | return fmt.Errorf("filepath.Abs failed for %s", path) 124 | } 125 | 126 | p = append(p, abs) 127 | } 128 | 129 | return nil 130 | } 131 | 132 | err := filepath.Walk(".", visit) 133 | if nil != err { 134 | return nil, err 135 | } 136 | 137 | return p, nil 138 | } 139 | 140 | func (tester *fileWriterTester) testCase(testCase *fileWriterTestCase, testNum int) { 141 | defer cleanupWriterTest(tester.t) 142 | 143 | tester.t.Logf("Start test [%v]\n", testNum) 144 | 145 | for _, filePath := range testCase.files { 146 | dir, _ := filepath.Split(filePath) 147 | 148 | var err error 149 | 150 | if 0 != len(dir) { 151 | err = os.MkdirAll(dir, defaultDirectoryPermissions) 152 | if err != nil { 153 | tester.t.Error(err) 154 | return 155 | } 156 | } 157 | 158 | fi, err := os.Create(filePath) 159 | if err != nil { 160 | tester.t.Error(err) 161 | return 162 | } 163 | 164 | err = fi.Close() 165 | if err != nil { 166 | tester.t.Error(err) 167 | return 168 | } 169 | } 170 | 171 | fwc, err := tester.writerGetter(testCase) 172 | if err != nil { 173 | tester.t.Error(err) 174 | return 175 | } 176 | defer fwc.Close() 177 | 178 | tester.performWrite(fwc, testCase.writeCount) 179 | 180 | files, err := getWriterTestResultFiles() 181 | if err != nil { 182 | tester.t.Error(err) 183 | return 184 | } 185 | 186 | tester.checkRequiredFilesExist(testCase, files) 187 | tester.checkJustRequiredFilesExist(testCase, files) 188 | 189 | } 190 | 191 | func (tester *fileWriterTester) test() { 192 | for i, tc := range tester.testCases { 193 | cleanupWriterTest(tester.t) 194 | tester.testCase(tc, i) 195 | } 196 | } 197 | 198 | func (tester *fileWriterTester) performWrite(fileWriter io.Writer, count int) { 199 | for i := 0; i < count; i++ { 200 | _, err := fileWriter.Write(bytesFileTest) 201 | 202 | if err != nil { 203 | tester.t.Error(err) 204 | return 205 | } 206 | } 207 | } 208 | 209 | func (tester *fileWriterTester) checkRequiredFilesExist(testCase *fileWriterTestCase, files []string) { 210 | var found bool 211 | for _, expected := range testCase.resFiles { 212 | found = false 213 | exAbs, err := filepath.Abs(expected) 214 | if err != nil { 215 | tester.t.Errorf("filepath.Abs failed for %s", expected) 216 | continue 217 | } 218 | 219 | for _, f := range files { 220 | if af, e := filepath.Abs(f); e == nil { 221 | tester.t.Log(af) 222 | if exAbs == af { 223 | found = true 224 | break 225 | } 226 | } else { 227 | tester.t.Errorf("filepath.Abs failed for %s", f) 228 | } 229 | } 230 | 231 | if !found { 232 | tester.t.Errorf("expected file: %s doesn't exist. Got %v\n", exAbs, files) 233 | } 234 | } 235 | } 236 | 237 | func (tester *fileWriterTester) checkJustRequiredFilesExist(testCase *fileWriterTestCase, files []string) { 238 | for _, f := range files { 239 | found := false 240 | for _, expected := range testCase.resFiles { 241 | 242 | exAbs, err := filepath.Abs(expected) 243 | if err != nil { 244 | tester.t.Errorf("filepath.Abs failed for %s", expected) 245 | } else { 246 | if exAbs == f { 247 | found = true 248 | break 249 | } 250 | } 251 | } 252 | 253 | if !found { 254 | tester.t.Errorf("unexpected file: %v", f) 255 | } 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /writers_formattedwriter.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "errors" 29 | "fmt" 30 | "io" 31 | ) 32 | 33 | type formattedWriter struct { 34 | writer io.Writer 35 | formatter *formatter 36 | } 37 | 38 | func NewFormattedWriter(writer io.Writer, formatter *formatter) (*formattedWriter, error) { 39 | if formatter == nil { 40 | return nil, errors.New("formatter can not be nil") 41 | } 42 | 43 | return &formattedWriter{writer, formatter}, nil 44 | } 45 | 46 | func (formattedWriter *formattedWriter) Write(message string, level LogLevel, context LogContextInterface) error { 47 | str := formattedWriter.formatter.Format(message, level, context) 48 | _, err := formattedWriter.writer.Write([]byte(str)) 49 | return err 50 | } 51 | 52 | func (formattedWriter *formattedWriter) String() string { 53 | return fmt.Sprintf("writer: %s, format: %s", formattedWriter.writer, formattedWriter.formatter) 54 | } 55 | 56 | func (formattedWriter *formattedWriter) Writer() io.Writer { 57 | return formattedWriter.writer 58 | } 59 | 60 | func (formattedWriter *formattedWriter) Format() *formatter { 61 | return formattedWriter.formatter 62 | } 63 | -------------------------------------------------------------------------------- /writers_formattedwriter_test.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "testing" 29 | ) 30 | 31 | func TestformattedWriter(t *testing.T) { 32 | formatStr := "%Level %LEVEL %Msg" 33 | message := "message" 34 | var logLevel = LogLevel(TraceLvl) 35 | 36 | bytesVerifier, err := newBytesVerifier(t) 37 | if err != nil { 38 | t.Error(err) 39 | return 40 | } 41 | 42 | formatter, err := NewFormatter(formatStr) 43 | if err != nil { 44 | t.Error(err) 45 | return 46 | } 47 | 48 | writer, err := NewFormattedWriter(bytesVerifier, formatter) 49 | if err != nil { 50 | t.Error(err) 51 | return 52 | } 53 | 54 | context, err := currentContext(nil) 55 | if err != nil { 56 | t.Error(err) 57 | return 58 | } 59 | 60 | logMessage := formatter.Format(message, logLevel, context) 61 | 62 | bytesVerifier.ExpectBytes([]byte(logMessage)) 63 | writer.Write(message, logLevel, context) 64 | bytesVerifier.MustNotExpect() 65 | } 66 | -------------------------------------------------------------------------------- /writers_rollingfilewriter_test.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "fmt" 29 | "io" 30 | "testing" 31 | ) 32 | 33 | // fileWriterTestCase is declared in writers_filewriter_test.go 34 | 35 | func createRollingSizeFileWriterTestCase( 36 | files []string, 37 | fileName string, 38 | fileSize int64, 39 | maxRolls int, 40 | writeCount int, 41 | resFiles []string, 42 | nameMode rollingNameMode, 43 | archiveType rollingArchiveType, 44 | archiveExploded bool, 45 | archivePath string) *fileWriterTestCase { 46 | 47 | return &fileWriterTestCase{files, fileName, rollingTypeSize, fileSize, maxRolls, "", writeCount, resFiles, nameMode, archiveType, archiveExploded, archivePath} 48 | } 49 | 50 | func createRollingDatefileWriterTestCase( 51 | files []string, 52 | fileName string, 53 | datePattern string, 54 | writeCount int, 55 | resFiles []string, 56 | nameMode rollingNameMode, 57 | archiveType rollingArchiveType, 58 | archiveExploded bool, 59 | archivePath string) *fileWriterTestCase { 60 | 61 | return &fileWriterTestCase{files, fileName, rollingTypeTime, 0, 0, datePattern, writeCount, resFiles, nameMode, archiveType, archiveExploded, archivePath} 62 | } 63 | 64 | func TestShouldArchiveWithTar(t *testing.T) { 65 | compressionType := compressionTypes[rollingArchiveGzip] 66 | 67 | archiveName := compressionType.rollingArchiveTypeName("log", false) 68 | 69 | if archiveName != "log.tar.gz" { 70 | t.Fatalf("archive name should be log.tar.gz but got %v", archiveName) 71 | } 72 | } 73 | 74 | func TestRollingFileWriter(t *testing.T) { 75 | t.Logf("Starting rolling file writer tests") 76 | NewFileWriterTester(rollingfileWriterTests, rollingFileWriterGetter, t).test() 77 | } 78 | 79 | //=============================================================== 80 | 81 | func rollingFileWriterGetter(testCase *fileWriterTestCase) (io.WriteCloser, error) { 82 | if testCase.rollingType == rollingTypeSize { 83 | return NewRollingFileWriterSize(testCase.fileName, testCase.archiveType, testCase.archivePath, testCase.fileSize, testCase.maxRolls, testCase.nameMode, testCase.archiveExploded) 84 | } else if testCase.rollingType == rollingTypeTime { 85 | return NewRollingFileWriterTime(testCase.fileName, testCase.archiveType, testCase.archivePath, -1, testCase.datePattern, testCase.nameMode, testCase.archiveExploded, false) 86 | } 87 | 88 | return nil, fmt.Errorf("incorrect rollingType") 89 | } 90 | 91 | //=============================================================== 92 | var rollingfileWriterTests = []*fileWriterTestCase{ 93 | createRollingSizeFileWriterTestCase([]string{}, "log.testlog", 10, 10, 1, []string{"log.testlog"}, rollingNameModePostfix, rollingArchiveNone, false, ""), 94 | createRollingSizeFileWriterTestCase([]string{}, "log.testlog", 10, 10, 2, []string{"log.testlog", "log.testlog.1"}, rollingNameModePostfix, rollingArchiveNone, false, ""), 95 | createRollingSizeFileWriterTestCase([]string{"1.log.testlog"}, "log.testlog", 10, 10, 2, []string{"log.testlog", "1.log.testlog", "2.log.testlog"}, rollingNameModePrefix, rollingArchiveNone, false, ""), 96 | createRollingSizeFileWriterTestCase([]string{"log.testlog.1"}, "log.testlog", 10, 1, 2, []string{"log.testlog", "log.testlog.2"}, rollingNameModePostfix, rollingArchiveNone, false, ""), 97 | createRollingSizeFileWriterTestCase([]string{}, "log.testlog", 10, 1, 2, []string{"log.testlog", "log.testlog.1"}, rollingNameModePostfix, rollingArchiveNone, false, ""), 98 | createRollingSizeFileWriterTestCase([]string{"log.testlog.9"}, "log.testlog", 10, 1, 2, []string{"log.testlog", "log.testlog.10"}, rollingNameModePostfix, rollingArchiveNone, false, ""), 99 | 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, ""), 100 | createRollingSizeFileWriterTestCase([]string{}, `dir/log.testlog`, 10, 10, 1, []string{`dir/log.testlog`}, rollingNameModePostfix, rollingArchiveNone, false, ""), 101 | createRollingSizeFileWriterTestCase([]string{}, `dir/log.testlog`, 10, 10, 2, []string{`dir/log.testlog`, `dir/1.log.testlog`}, rollingNameModePrefix, rollingArchiveNone, false, ""), 102 | 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, ""), 103 | 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, ""), 104 | createRollingSizeFileWriterTestCase([]string{}, `./log.testlog`, 10, 1, 2, []string{`log.testlog`, `log.testlog.1`}, rollingNameModePostfix, rollingArchiveNone, false, ""), 105 | createRollingSizeFileWriterTestCase([]string{`././././log.testlog.9`}, `log.testlog`, 10, 1, 2, []string{`log.testlog`, `log.testlog.10`}, rollingNameModePostfix, rollingArchiveNone, false, ""), 106 | 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, ""), 107 | createRollingSizeFileWriterTestCase([]string{}, `././dir/log.testlog`, 10, 10, 1, []string{`dir/log.testlog`}, rollingNameModePostfix, rollingArchiveNone, false, ""), 108 | createRollingSizeFileWriterTestCase([]string{}, `././dir/log.testlog`, 10, 10, 2, []string{`dir/log.testlog`, `dir/log.testlog.1`}, rollingNameModePostfix, rollingArchiveNone, false, ""), 109 | 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, ""), 110 | 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, ""), 111 | createRollingSizeFileWriterTestCase([]string{}, `././log.testlog`, 10, 1, 2, []string{`log.testlog`, `log.testlog.1`}, rollingNameModePostfix, rollingArchiveNone, false, ""), 112 | createRollingSizeFileWriterTestCase([]string{`././././log.testlog.9`}, `log.testlog`, 10, 1, 2, []string{`log.testlog`, `log.testlog.10`}, rollingNameModePostfix, rollingArchiveNone, false, ""), 113 | 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, ""), 114 | 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"), 115 | // ==================== 116 | } 117 | -------------------------------------------------------------------------------- /writers_smtpwriter.go: -------------------------------------------------------------------------------- 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 | 25 | package seelog 26 | 27 | import ( 28 | "crypto/tls" 29 | "crypto/x509" 30 | "errors" 31 | "fmt" 32 | "io/ioutil" 33 | "net/smtp" 34 | "path/filepath" 35 | "strings" 36 | ) 37 | 38 | const ( 39 | // Default subject phrase for sending emails. 40 | DefaultSubjectPhrase = "Diagnostic message from server: " 41 | 42 | // Message subject pattern composed according to RFC 5321. 43 | rfc5321SubjectPattern = "From: %s <%s>\nSubject: %s\n\n" 44 | ) 45 | 46 | // smtpWriter is used to send emails via given SMTP-server. 47 | type smtpWriter struct { 48 | auth smtp.Auth 49 | hostName string 50 | hostPort string 51 | hostNameWithPort string 52 | senderAddress string 53 | senderName string 54 | recipientAddresses []string 55 | caCertDirPaths []string 56 | mailHeaders []string 57 | subject string 58 | } 59 | 60 | // NewSMTPWriter returns a new SMTP-writer. 61 | func NewSMTPWriter(sa, sn string, ras []string, hn, hp, un, pwd string, cacdps []string, subj string, headers []string) *smtpWriter { 62 | return &smtpWriter{ 63 | auth: smtp.PlainAuth("", un, pwd, hn), 64 | hostName: hn, 65 | hostPort: hp, 66 | hostNameWithPort: fmt.Sprintf("%s:%s", hn, hp), 67 | senderAddress: sa, 68 | senderName: sn, 69 | recipientAddresses: ras, 70 | caCertDirPaths: cacdps, 71 | subject: subj, 72 | mailHeaders: headers, 73 | } 74 | } 75 | 76 | func prepareMessage(senderAddr, senderName, subject string, body []byte, headers []string) []byte { 77 | headerLines := fmt.Sprintf(rfc5321SubjectPattern, senderName, senderAddr, subject) 78 | // Build header lines if configured. 79 | if headers != nil && len(headers) > 0 { 80 | headerLines += strings.Join(headers, "\n") 81 | headerLines += "\n" 82 | } 83 | return append([]byte(headerLines), body...) 84 | } 85 | 86 | // getTLSConfig gets paths of PEM files with certificates, 87 | // host server name and tries to create an appropriate TLS.Config. 88 | func getTLSConfig(pemFileDirPaths []string, hostName string) (config *tls.Config, err error) { 89 | if pemFileDirPaths == nil || len(pemFileDirPaths) == 0 { 90 | err = errors.New("invalid PEM file paths") 91 | return 92 | } 93 | pemEncodedContent := []byte{} 94 | var ( 95 | e error 96 | bytes []byte 97 | ) 98 | // Create a file-filter-by-extension, set aside non-pem files. 99 | pemFilePathFilter := func(fp string) bool { 100 | if filepath.Ext(fp) == ".pem" { 101 | return true 102 | } 103 | return false 104 | } 105 | for _, pemFileDirPath := range pemFileDirPaths { 106 | pemFilePaths, err := getDirFilePaths(pemFileDirPath, pemFilePathFilter, false) 107 | if err != nil { 108 | return nil, err 109 | } 110 | 111 | // Put together all the PEM files to decode them as a whole byte slice. 112 | for _, pfp := range pemFilePaths { 113 | if bytes, e = ioutil.ReadFile(pfp); e == nil { 114 | pemEncodedContent = append(pemEncodedContent, bytes...) 115 | } else { 116 | return nil, fmt.Errorf("cannot read file: %s: %s", pfp, e.Error()) 117 | } 118 | } 119 | } 120 | config = &tls.Config{RootCAs: x509.NewCertPool(), ServerName: hostName} 121 | isAppended := config.RootCAs.AppendCertsFromPEM(pemEncodedContent) 122 | if !isAppended { 123 | // Extract this into a separate error. 124 | err = errors.New("invalid PEM content") 125 | return 126 | } 127 | return 128 | } 129 | 130 | // SendMail accepts TLS configuration, connects to the server at addr, 131 | // switches to TLS if possible, authenticates with mechanism a if possible, 132 | // and then sends an email from address from, to addresses to, with message msg. 133 | func sendMailWithTLSConfig(config *tls.Config, addr string, a smtp.Auth, from string, to []string, msg []byte) error { 134 | c, err := smtp.Dial(addr) 135 | if err != nil { 136 | return err 137 | } 138 | // Check if the server supports STARTTLS extension. 139 | if ok, _ := c.Extension("STARTTLS"); ok { 140 | if err = c.StartTLS(config); err != nil { 141 | return err 142 | } 143 | } 144 | // Check if the server supports AUTH extension and use given smtp.Auth. 145 | if a != nil { 146 | if isSupported, _ := c.Extension("AUTH"); isSupported { 147 | if err = c.Auth(a); err != nil { 148 | return err 149 | } 150 | } 151 | } 152 | // Portion of code from the official smtp.SendMail function, 153 | // see http://golang.org/src/pkg/net/smtp/smtp.go. 154 | if err = c.Mail(from); err != nil { 155 | return err 156 | } 157 | for _, addr := range to { 158 | if err = c.Rcpt(addr); err != nil { 159 | return err 160 | } 161 | } 162 | w, err := c.Data() 163 | if err != nil { 164 | return err 165 | } 166 | _, err = w.Write(msg) 167 | if err != nil { 168 | return err 169 | } 170 | err = w.Close() 171 | if err != nil { 172 | return err 173 | } 174 | return c.Quit() 175 | } 176 | 177 | // Write pushes a text message properly composed according to RFC 5321 178 | // to a post server, which sends it to the recipients. 179 | func (smtpw *smtpWriter) Write(data []byte) (int, error) { 180 | var err error 181 | 182 | if smtpw.caCertDirPaths == nil { 183 | err = smtp.SendMail( 184 | smtpw.hostNameWithPort, 185 | smtpw.auth, 186 | smtpw.senderAddress, 187 | smtpw.recipientAddresses, 188 | prepareMessage(smtpw.senderAddress, smtpw.senderName, smtpw.subject, data, smtpw.mailHeaders), 189 | ) 190 | } else { 191 | config, e := getTLSConfig(smtpw.caCertDirPaths, smtpw.hostName) 192 | if e != nil { 193 | return 0, e 194 | } 195 | err = sendMailWithTLSConfig( 196 | config, 197 | smtpw.hostNameWithPort, 198 | smtpw.auth, 199 | smtpw.senderAddress, 200 | smtpw.recipientAddresses, 201 | prepareMessage(smtpw.senderAddress, smtpw.senderName, smtpw.subject, data, smtpw.mailHeaders), 202 | ) 203 | } 204 | if err != nil { 205 | return 0, err 206 | } 207 | return len(data), nil 208 | } 209 | 210 | // Close closes down SMTP-connection. 211 | func (smtpw *smtpWriter) Close() error { 212 | // Do nothing as Write method opens and closes connection automatically. 213 | return nil 214 | } 215 | --------------------------------------------------------------------------------