├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTORS ├── LICENSE ├── README.md ├── backend.go ├── color ├── color.go ├── colorable_others.go ├── colorable_windows.go ├── isatty_appengine.go ├── isatty_bsd.go ├── isatty_linux.go ├── isatty_solaris.go └── isatty_windows.go ├── console.go ├── console_test.go ├── example_test.go ├── examples ├── example.go └── example.png ├── file.go ├── file_test.go ├── format.go ├── format_test.go ├── level.go ├── level_test.go ├── logger.go ├── logger_test.go ├── memory.go ├── memory_test.go ├── multi.go └── multi_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.a 3 | *.so 4 | _obj 5 | _test 6 | *.[568vq] 7 | [568vq].out 8 | *.cgo1.go 9 | *.cgo2.c 10 | _cgo_defun.c 11 | _cgo_gotypes.go 12 | _cgo_export.* 13 | _testmain.go 14 | *.exe 15 | *.exe~ 16 | *.test 17 | *.prof 18 | *.rar 19 | *.zip 20 | *.gz 21 | *.psd 22 | *.bmd 23 | *.cfg 24 | *.pptx 25 | *.log 26 | *nohup.out 27 | *.sublime-project 28 | *.sublime-workspace 29 | 30 | examples/examples -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.0 5 | - 1.1 6 | - tip 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 2.0.0-rc1 (2016-02-11) 4 | 5 | Time flies and it has been three years since this package was first released. 6 | There have been a couple of API changes I have wanted to do for some time but 7 | I've tried to maintain backwards compatibility. Some inconsistencies in the 8 | API have started to show, proper vendor support in Go out of the box and 9 | the fact that `go vet` will give warnings -- I have decided to bump the major 10 | version. 11 | 12 | * Make eg. `Info` and `Infof` do different things. You want to change all calls 13 | to `Info` with a string format go to `Infof` etc. In many cases, `go vet` will 14 | guide you. 15 | * `Id` in `Record` is now called `ID` 16 | 17 | ## 1.0.0 (2013-02-21) 18 | 19 | Initial release 20 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Alec Thomas 2 | Guilhem Lettron 3 | Ivan Daniluk 4 | Nimi Wariboko Jr 5 | Róbert Selvek 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Örjan Persson. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Golang logging library 2 | 3 | [![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/henrylee2cn/go-logging) [![build](https://img.shields.io/travis/henrylee2cn/go-logging.svg?style=flat)](https://travis-ci.org/henrylee2cn/go-logging) 4 | 5 | Package logging implements a logging infrastructure for Go. Its output format 6 | is customizable and supports different logging backends like syslog, file and 7 | memory. Multiple backends can be utilized with different log levels per backend 8 | and logger. 9 | 10 | **_NOTE:_** backwards compatibility promise have been dropped for master. Please 11 | vendor this package or use `gopkg.in/henrylee2cn/go-logging.v1` for previous version. See 12 | [changelog](CHANGELOG.md) for details. 13 | 14 | ## Example 15 | 16 | Let's have a look at an [example](examples/example.go) which demonstrates most 17 | of the features found in this library. 18 | 19 | [![Example Output](examples/example.png)](examples/example.go) 20 | 21 | ```go 22 | package main 23 | 24 | import ( 25 | "os" 26 | 27 | "github.com/henrylee2cn/go-logging" 28 | ) 29 | 30 | var log = logging.NewLogger("example") 31 | 32 | // Example format string. Everything except the message has a custom color 33 | // which is dependent on the log level. Many fields have a custom output 34 | // formatting too, eg. the time returns the hour down to the milli second. 35 | var format = logging.MustStringFormatter( 36 | `%{color}%{time:15:04:05.000} %{shortfunc} ▶ %{level:.4s} %{id:03x}%{color:reset} %{message}`, 37 | ) 38 | 39 | // Password is just an example type implementing the Redactor interface. Any 40 | // time this is logged, the Redacted() function will be called. 41 | type Password string 42 | 43 | func (p Password) Redacted() interface{} { 44 | return logging.Redact(string(p)) 45 | } 46 | 47 | func main() { 48 | // For demo purposes, create two backend for os.Stderr. 49 | backend1 := logging.NewLogBackend(os.Stderr, "", 0) 50 | backend2 := logging.NewLogBackend(os.Stderr, "", 0) 51 | 52 | // For messages written to backend2 we want to add some additional 53 | // information to the output, including the used log level and the name of 54 | // the function. 55 | backend2Formatter := logging.NewBackendFormatter(backend2, format) 56 | 57 | // Only errors and more severe messages should be sent to backend1 58 | backend1Leveled := logging.AddModuleLevel(backend1) 59 | backend1Leveled.SetLevel(logging.ERROR, "") 60 | 61 | // Set the backends to be used. 62 | logging.SetBackend(backend1Leveled, backend2Formatter) 63 | 64 | log.Debugf("debug %s", Password("secret")) 65 | log.Info("info") 66 | log.Notice("notice") 67 | log.Warning("warning") 68 | log.Error("err") 69 | log.Critical("crit") 70 | } 71 | ``` 72 | 73 | ## Installing 74 | 75 | ### Using *go get* 76 | 77 | $ go get github.com/henrylee2cn/go-logging 78 | 79 | After this command *go-logging* is ready to use. Its source will be in: 80 | 81 | $GOPATH/src/pkg/github.com/henrylee2cn/go-logging 82 | 83 | You can use `go get -u` to update the package. 84 | 85 | ## Documentation 86 | 87 | For docs, see http://godoc.org/github.com/henrylee2cn/go-logging or run: 88 | 89 | $ godoc github.com/henrylee2cn/go-logging 90 | 91 | ## Additional resources 92 | 93 | * [wslog](https://godoc.org/github.com/cryptix/exp/wslog) -- exposes log messages through a WebSocket. 94 | -------------------------------------------------------------------------------- /backend.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Örjan Persson. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package logging 6 | 7 | // defaultBackend is the backend used for all logging calls. 8 | var defaultBackend LeveledBackend 9 | 10 | // Backend is the interface which a log backend need to implement to be able to 11 | // be used as a logging backend. 12 | type Backend interface { 13 | Log(int, *Record) 14 | Close() 15 | } 16 | 17 | // SetBackend replaces the backend currently set with the given new logging 18 | // backend. 19 | func SetBackend(backends ...Backend) LeveledBackend { 20 | var backend Backend 21 | if len(backends) == 1 { 22 | backend = backends[0] 23 | } else { 24 | backend = MultiLogger(backends...) 25 | } 26 | 27 | defaultBackend = AddModuleLevel(backend) 28 | return defaultBackend 29 | } 30 | 31 | // SetLevel sets the logging level for the specified module. The module 32 | // corresponds to the string specified in NewLogger. 33 | func SetLevel(level Level, module string) { 34 | defaultBackend.SetLevel(level, module) 35 | } 36 | 37 | // GetLevel returns the logging level for the specified module. 38 | func GetLevel(module string) Level { 39 | return defaultBackend.GetLevel(module) 40 | } 41 | -------------------------------------------------------------------------------- /color/color.go: -------------------------------------------------------------------------------- 1 | package color 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | ) 7 | 8 | type ( 9 | inner func(interface{}, []string, *Color) string 10 | ) 11 | 12 | // Color styles 13 | const ( 14 | // Blk Black text style 15 | Blk = "30" 16 | // Rd red text style 17 | Rd = "31" 18 | // Grn green text style 19 | Grn = "32" 20 | // Yel yellow text style 21 | Yel = "33" 22 | // Blu blue text style 23 | Blu = "34" 24 | // Mgn magenta text style 25 | Mgn = "35" 26 | // Cyn cyan text style 27 | Cyn = "36" 28 | // Wht white text style 29 | Wht = "37" 30 | // Gry grey text style 31 | Gry = "90" 32 | 33 | // BlkBg black background style 34 | BlkBg = "40" 35 | // RdBg red background style 36 | RdBg = "41" 37 | // GrnBg green background style 38 | GrnBg = "42" 39 | // YelBg yellow background style 40 | YelBg = "43" 41 | // BluBg blue background style 42 | BluBg = "44" 43 | // MgnBg magenta background style 44 | MgnBg = "45" 45 | // CynBg cyan background style 46 | CynBg = "46" 47 | // WhtBg white background style 48 | WhtBg = "47" 49 | 50 | // R reset emphasis style 51 | R = "0" 52 | // B bold emphasis style 53 | B = "1" 54 | // D dim emphasis style 55 | D = "2" 56 | // I italic emphasis style 57 | I = "3" 58 | // U underline emphasis style 59 | U = "4" 60 | // In inverse emphasis style 61 | In = "7" 62 | // H hidden emphasis style 63 | H = "8" 64 | // S strikeout emphasis style 65 | S = "9" 66 | ) 67 | 68 | var ( 69 | black = outer(Blk) 70 | red = outer(Rd) 71 | green = outer(Grn) 72 | yellow = outer(Yel) 73 | blue = outer(Blu) 74 | magenta = outer(Mgn) 75 | cyan = outer(Cyn) 76 | white = outer(Wht) 77 | grey = outer(Gry) 78 | 79 | blackBg = outer(BlkBg) 80 | redBg = outer(RdBg) 81 | greenBg = outer(GrnBg) 82 | yellowBg = outer(YelBg) 83 | blueBg = outer(BluBg) 84 | magentaBg = outer(MgnBg) 85 | cyanBg = outer(CynBg) 86 | whiteBg = outer(WhtBg) 87 | 88 | reset = outer(R) 89 | bolt = outer(B) 90 | dim = outer(D) 91 | italic = outer(I) 92 | underline = outer(U) 93 | inverse = outer(In) 94 | hidden = outer(H) 95 | strikeout = outer(S) 96 | 97 | global = New() 98 | ) 99 | 100 | func outer(n string) inner { 101 | return func(msg interface{}, styles []string, c *Color) string { 102 | // TODO: May be drop fmt to boost performance 103 | if c.disabled { 104 | return fmt.Sprintf("%v", msg) 105 | } 106 | 107 | b := new(bytes.Buffer) 108 | b.WriteString("\x1b[") 109 | b.WriteString(n) 110 | for _, s := range styles { 111 | b.WriteString(";") 112 | b.WriteString(s) 113 | } 114 | b.WriteString("m") 115 | return fmt.Sprintf("%s%v\x1b[0m", b.String(), msg) 116 | } 117 | } 118 | 119 | type ( 120 | Color struct { 121 | disabled bool 122 | } 123 | ) 124 | 125 | // New creates a Color instance. 126 | func New() *Color { 127 | return &Color{} 128 | } 129 | 130 | // Disable disables the colors and styles. 131 | func (c *Color) Disable() { 132 | c.disabled = true 133 | } 134 | 135 | // Enable enables the colors and styles. 136 | func (c *Color) Enable() { 137 | c.disabled = false 138 | } 139 | 140 | func (c *Color) Black(msg interface{}, styles ...string) string { 141 | return black(msg, styles, c) 142 | } 143 | 144 | func (c *Color) Red(msg interface{}, styles ...string) string { 145 | return red(msg, styles, c) 146 | } 147 | 148 | func (c *Color) Green(msg interface{}, styles ...string) string { 149 | return green(msg, styles, c) 150 | } 151 | 152 | func (c *Color) Yellow(msg interface{}, styles ...string) string { 153 | return yellow(msg, styles, c) 154 | } 155 | 156 | func (c *Color) Blue(msg interface{}, styles ...string) string { 157 | return blue(msg, styles, c) 158 | } 159 | 160 | func (c *Color) Magenta(msg interface{}, styles ...string) string { 161 | return magenta(msg, styles, c) 162 | } 163 | 164 | func (c *Color) Cyan(msg interface{}, styles ...string) string { 165 | return cyan(msg, styles, c) 166 | } 167 | 168 | func (c *Color) White(msg interface{}, styles ...string) string { 169 | return white(msg, styles, c) 170 | } 171 | 172 | func (c *Color) Grey(msg interface{}, styles ...string) string { 173 | return grey(msg, styles, c) 174 | } 175 | 176 | func (c *Color) BlackBg(msg interface{}, styles ...string) string { 177 | return blackBg(msg, styles, c) 178 | } 179 | 180 | func (c *Color) RedBg(msg interface{}, styles ...string) string { 181 | return redBg(msg, styles, c) 182 | } 183 | 184 | func (c *Color) GreenBg(msg interface{}, styles ...string) string { 185 | return greenBg(msg, styles, c) 186 | } 187 | 188 | func (c *Color) YellowBg(msg interface{}, styles ...string) string { 189 | return yellowBg(msg, styles, c) 190 | } 191 | 192 | func (c *Color) BlueBg(msg interface{}, styles ...string) string { 193 | return blueBg(msg, styles, c) 194 | } 195 | 196 | func (c *Color) MagentaBg(msg interface{}, styles ...string) string { 197 | return magentaBg(msg, styles, c) 198 | } 199 | 200 | func (c *Color) CyanBg(msg interface{}, styles ...string) string { 201 | return cyanBg(msg, styles, c) 202 | } 203 | 204 | func (c *Color) WhiteBg(msg interface{}, styles ...string) string { 205 | return whiteBg(msg, styles, c) 206 | } 207 | 208 | func (c *Color) Reset(msg interface{}, styles ...string) string { 209 | return reset(msg, styles, c) 210 | } 211 | 212 | func (c *Color) Bold(msg interface{}, styles ...string) string { 213 | return bolt(msg, styles, c) 214 | } 215 | 216 | func (c *Color) Dim(msg interface{}, styles ...string) string { 217 | return dim(msg, styles, c) 218 | } 219 | 220 | func (c *Color) Italic(msg interface{}, styles ...string) string { 221 | return italic(msg, styles, c) 222 | } 223 | 224 | func (c *Color) Underline(msg interface{}, styles ...string) string { 225 | return underline(msg, styles, c) 226 | } 227 | 228 | func (c *Color) Inverse(msg interface{}, styles ...string) string { 229 | return inverse(msg, styles, c) 230 | } 231 | 232 | func (c *Color) Hidden(msg interface{}, styles ...string) string { 233 | return hidden(msg, styles, c) 234 | } 235 | 236 | func (c *Color) Strikeout(msg interface{}, styles ...string) string { 237 | return strikeout(msg, styles, c) 238 | } 239 | 240 | func Disable() { 241 | global.disabled = true 242 | } 243 | 244 | func Enable() { 245 | global.disabled = false 246 | } 247 | 248 | func Black(msg interface{}, styles ...string) string { 249 | return global.Black(msg, styles...) 250 | } 251 | 252 | func Red(msg interface{}, styles ...string) string { 253 | return global.Red(msg, styles...) 254 | } 255 | 256 | func Green(msg interface{}, styles ...string) string { 257 | return global.Green(msg, styles...) 258 | } 259 | 260 | func Yellow(msg interface{}, styles ...string) string { 261 | return global.Yellow(msg, styles...) 262 | } 263 | 264 | func Blue(msg interface{}, styles ...string) string { 265 | return global.Blue(msg, styles...) 266 | } 267 | 268 | func Magenta(msg interface{}, styles ...string) string { 269 | return global.Magenta(msg, styles...) 270 | } 271 | 272 | func Cyan(msg interface{}, styles ...string) string { 273 | return global.Cyan(msg, styles...) 274 | } 275 | 276 | func White(msg interface{}, styles ...string) string { 277 | return global.White(msg, styles...) 278 | } 279 | 280 | func Grey(msg interface{}, styles ...string) string { 281 | return global.Grey(msg, styles...) 282 | } 283 | 284 | func BlackBg(msg interface{}, styles ...string) string { 285 | return global.BlackBg(msg, styles...) 286 | } 287 | 288 | func RedBg(msg interface{}, styles ...string) string { 289 | return global.RedBg(msg, styles...) 290 | } 291 | 292 | func GreenBg(msg interface{}, styles ...string) string { 293 | return global.GreenBg(msg, styles...) 294 | } 295 | 296 | func YellowBg(msg interface{}, styles ...string) string { 297 | return global.YellowBg(msg, styles...) 298 | } 299 | 300 | func BlueBg(msg interface{}, styles ...string) string { 301 | return global.BlueBg(msg, styles...) 302 | } 303 | 304 | func MagentaBg(msg interface{}, styles ...string) string { 305 | return global.MagentaBg(msg, styles...) 306 | } 307 | 308 | func CyanBg(msg interface{}, styles ...string) string { 309 | return global.CyanBg(msg, styles...) 310 | } 311 | 312 | func WhiteBg(msg interface{}, styles ...string) string { 313 | return global.WhiteBg(msg, styles...) 314 | } 315 | 316 | func Reset(msg interface{}, styles ...string) string { 317 | return global.Reset(msg, styles...) 318 | } 319 | 320 | func Bold(msg interface{}, styles ...string) string { 321 | return global.Bold(msg, styles...) 322 | } 323 | 324 | func Dim(msg interface{}, styles ...string) string { 325 | return global.Dim(msg, styles...) 326 | } 327 | 328 | func Italic(msg interface{}, styles ...string) string { 329 | return global.Italic(msg, styles...) 330 | } 331 | 332 | func Underline(msg interface{}, styles ...string) string { 333 | return global.Underline(msg, styles...) 334 | } 335 | 336 | func Inverse(msg interface{}, styles ...string) string { 337 | return global.Inverse(msg, styles...) 338 | } 339 | 340 | func Hidden(msg interface{}, styles ...string) string { 341 | return global.Hidden(msg, styles...) 342 | } 343 | 344 | func Strikeout(msg interface{}, styles ...string) string { 345 | return global.Strikeout(msg, styles...) 346 | } 347 | -------------------------------------------------------------------------------- /color/colorable_others.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package color 4 | 5 | import ( 6 | "io" 7 | "os" 8 | ) 9 | 10 | func NewColorable(file *os.File) io.Writer { 11 | if file == nil { 12 | panic("nil passed instead of *os.File to NewColorable()") 13 | } 14 | 15 | return file 16 | } 17 | 18 | func NewColorableStdout() io.Writer { 19 | return os.Stdout 20 | } 21 | 22 | func NewColorableStderr() io.Writer { 23 | return os.Stderr 24 | } 25 | -------------------------------------------------------------------------------- /color/colorable_windows.go: -------------------------------------------------------------------------------- 1 | package color 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "math" 8 | "os" 9 | "strconv" 10 | "strings" 11 | "syscall" 12 | "unsafe" 13 | ) 14 | 15 | const ( 16 | foregroundBlue = 0x1 17 | foregroundGreen = 0x2 18 | foregroundRed = 0x4 19 | foregroundIntensity = 0x8 20 | foregroundMask = (foregroundRed | foregroundBlue | foregroundGreen | foregroundIntensity) 21 | backgroundBlue = 0x10 22 | backgroundGreen = 0x20 23 | backgroundRed = 0x40 24 | backgroundIntensity = 0x80 25 | backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity) 26 | ) 27 | 28 | type wchar uint16 29 | type short int16 30 | type dword uint32 31 | type word uint16 32 | 33 | type coord struct { 34 | x short 35 | y short 36 | } 37 | 38 | type smallRect struct { 39 | left short 40 | top short 41 | right short 42 | bottom short 43 | } 44 | 45 | type consoleScreenBufferInfo struct { 46 | size coord 47 | cursorPosition coord 48 | attributes word 49 | window smallRect 50 | maximumWindowSize coord 51 | } 52 | 53 | var ( 54 | // kernel32 = syscall.NewLazyDLL("kernel32.dll") 55 | procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") 56 | procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute") 57 | procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition") 58 | procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW") 59 | procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute") 60 | ) 61 | 62 | type Writer struct { 63 | out io.Writer 64 | handle syscall.Handle 65 | lastbuf bytes.Buffer 66 | oldattr word 67 | } 68 | 69 | func NewColorable(file *os.File) io.Writer { 70 | if file == nil { 71 | panic("nil passed instead of *os.File to NewColorable()") 72 | } 73 | 74 | if IsTerminal(file.Fd()) { 75 | var csbi consoleScreenBufferInfo 76 | handle := syscall.Handle(file.Fd()) 77 | procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) 78 | return &Writer{out: file, handle: handle, oldattr: csbi.attributes} 79 | } else { 80 | return file 81 | } 82 | } 83 | 84 | func NewColorableStdout() io.Writer { 85 | return NewColorable(os.Stdout) 86 | } 87 | 88 | func NewColorableStderr() io.Writer { 89 | return NewColorable(os.Stderr) 90 | } 91 | 92 | var color256 = map[int]int{ 93 | 0: 0x000000, 94 | 1: 0x800000, 95 | 2: 0x008000, 96 | 3: 0x808000, 97 | 4: 0x000080, 98 | 5: 0x800080, 99 | 6: 0x008080, 100 | 7: 0xc0c0c0, 101 | 8: 0x808080, 102 | 9: 0xff0000, 103 | 10: 0x00ff00, 104 | 11: 0xffff00, 105 | 12: 0x0000ff, 106 | 13: 0xff00ff, 107 | 14: 0x00ffff, 108 | 15: 0xffffff, 109 | 16: 0x000000, 110 | 17: 0x00005f, 111 | 18: 0x000087, 112 | 19: 0x0000af, 113 | 20: 0x0000d7, 114 | 21: 0x0000ff, 115 | 22: 0x005f00, 116 | 23: 0x005f5f, 117 | 24: 0x005f87, 118 | 25: 0x005faf, 119 | 26: 0x005fd7, 120 | 27: 0x005fff, 121 | 28: 0x008700, 122 | 29: 0x00875f, 123 | 30: 0x008787, 124 | 31: 0x0087af, 125 | 32: 0x0087d7, 126 | 33: 0x0087ff, 127 | 34: 0x00af00, 128 | 35: 0x00af5f, 129 | 36: 0x00af87, 130 | 37: 0x00afaf, 131 | 38: 0x00afd7, 132 | 39: 0x00afff, 133 | 40: 0x00d700, 134 | 41: 0x00d75f, 135 | 42: 0x00d787, 136 | 43: 0x00d7af, 137 | 44: 0x00d7d7, 138 | 45: 0x00d7ff, 139 | 46: 0x00ff00, 140 | 47: 0x00ff5f, 141 | 48: 0x00ff87, 142 | 49: 0x00ffaf, 143 | 50: 0x00ffd7, 144 | 51: 0x00ffff, 145 | 52: 0x5f0000, 146 | 53: 0x5f005f, 147 | 54: 0x5f0087, 148 | 55: 0x5f00af, 149 | 56: 0x5f00d7, 150 | 57: 0x5f00ff, 151 | 58: 0x5f5f00, 152 | 59: 0x5f5f5f, 153 | 60: 0x5f5f87, 154 | 61: 0x5f5faf, 155 | 62: 0x5f5fd7, 156 | 63: 0x5f5fff, 157 | 64: 0x5f8700, 158 | 65: 0x5f875f, 159 | 66: 0x5f8787, 160 | 67: 0x5f87af, 161 | 68: 0x5f87d7, 162 | 69: 0x5f87ff, 163 | 70: 0x5faf00, 164 | 71: 0x5faf5f, 165 | 72: 0x5faf87, 166 | 73: 0x5fafaf, 167 | 74: 0x5fafd7, 168 | 75: 0x5fafff, 169 | 76: 0x5fd700, 170 | 77: 0x5fd75f, 171 | 78: 0x5fd787, 172 | 79: 0x5fd7af, 173 | 80: 0x5fd7d7, 174 | 81: 0x5fd7ff, 175 | 82: 0x5fff00, 176 | 83: 0x5fff5f, 177 | 84: 0x5fff87, 178 | 85: 0x5fffaf, 179 | 86: 0x5fffd7, 180 | 87: 0x5fffff, 181 | 88: 0x870000, 182 | 89: 0x87005f, 183 | 90: 0x870087, 184 | 91: 0x8700af, 185 | 92: 0x8700d7, 186 | 93: 0x8700ff, 187 | 94: 0x875f00, 188 | 95: 0x875f5f, 189 | 96: 0x875f87, 190 | 97: 0x875faf, 191 | 98: 0x875fd7, 192 | 99: 0x875fff, 193 | 100: 0x878700, 194 | 101: 0x87875f, 195 | 102: 0x878787, 196 | 103: 0x8787af, 197 | 104: 0x8787d7, 198 | 105: 0x8787ff, 199 | 106: 0x87af00, 200 | 107: 0x87af5f, 201 | 108: 0x87af87, 202 | 109: 0x87afaf, 203 | 110: 0x87afd7, 204 | 111: 0x87afff, 205 | 112: 0x87d700, 206 | 113: 0x87d75f, 207 | 114: 0x87d787, 208 | 115: 0x87d7af, 209 | 116: 0x87d7d7, 210 | 117: 0x87d7ff, 211 | 118: 0x87ff00, 212 | 119: 0x87ff5f, 213 | 120: 0x87ff87, 214 | 121: 0x87ffaf, 215 | 122: 0x87ffd7, 216 | 123: 0x87ffff, 217 | 124: 0xaf0000, 218 | 125: 0xaf005f, 219 | 126: 0xaf0087, 220 | 127: 0xaf00af, 221 | 128: 0xaf00d7, 222 | 129: 0xaf00ff, 223 | 130: 0xaf5f00, 224 | 131: 0xaf5f5f, 225 | 132: 0xaf5f87, 226 | 133: 0xaf5faf, 227 | 134: 0xaf5fd7, 228 | 135: 0xaf5fff, 229 | 136: 0xaf8700, 230 | 137: 0xaf875f, 231 | 138: 0xaf8787, 232 | 139: 0xaf87af, 233 | 140: 0xaf87d7, 234 | 141: 0xaf87ff, 235 | 142: 0xafaf00, 236 | 143: 0xafaf5f, 237 | 144: 0xafaf87, 238 | 145: 0xafafaf, 239 | 146: 0xafafd7, 240 | 147: 0xafafff, 241 | 148: 0xafd700, 242 | 149: 0xafd75f, 243 | 150: 0xafd787, 244 | 151: 0xafd7af, 245 | 152: 0xafd7d7, 246 | 153: 0xafd7ff, 247 | 154: 0xafff00, 248 | 155: 0xafff5f, 249 | 156: 0xafff87, 250 | 157: 0xafffaf, 251 | 158: 0xafffd7, 252 | 159: 0xafffff, 253 | 160: 0xd70000, 254 | 161: 0xd7005f, 255 | 162: 0xd70087, 256 | 163: 0xd700af, 257 | 164: 0xd700d7, 258 | 165: 0xd700ff, 259 | 166: 0xd75f00, 260 | 167: 0xd75f5f, 261 | 168: 0xd75f87, 262 | 169: 0xd75faf, 263 | 170: 0xd75fd7, 264 | 171: 0xd75fff, 265 | 172: 0xd78700, 266 | 173: 0xd7875f, 267 | 174: 0xd78787, 268 | 175: 0xd787af, 269 | 176: 0xd787d7, 270 | 177: 0xd787ff, 271 | 178: 0xd7af00, 272 | 179: 0xd7af5f, 273 | 180: 0xd7af87, 274 | 181: 0xd7afaf, 275 | 182: 0xd7afd7, 276 | 183: 0xd7afff, 277 | 184: 0xd7d700, 278 | 185: 0xd7d75f, 279 | 186: 0xd7d787, 280 | 187: 0xd7d7af, 281 | 188: 0xd7d7d7, 282 | 189: 0xd7d7ff, 283 | 190: 0xd7ff00, 284 | 191: 0xd7ff5f, 285 | 192: 0xd7ff87, 286 | 193: 0xd7ffaf, 287 | 194: 0xd7ffd7, 288 | 195: 0xd7ffff, 289 | 196: 0xff0000, 290 | 197: 0xff005f, 291 | 198: 0xff0087, 292 | 199: 0xff00af, 293 | 200: 0xff00d7, 294 | 201: 0xff00ff, 295 | 202: 0xff5f00, 296 | 203: 0xff5f5f, 297 | 204: 0xff5f87, 298 | 205: 0xff5faf, 299 | 206: 0xff5fd7, 300 | 207: 0xff5fff, 301 | 208: 0xff8700, 302 | 209: 0xff875f, 303 | 210: 0xff8787, 304 | 211: 0xff87af, 305 | 212: 0xff87d7, 306 | 213: 0xff87ff, 307 | 214: 0xffaf00, 308 | 215: 0xffaf5f, 309 | 216: 0xffaf87, 310 | 217: 0xffafaf, 311 | 218: 0xffafd7, 312 | 219: 0xffafff, 313 | 220: 0xffd700, 314 | 221: 0xffd75f, 315 | 222: 0xffd787, 316 | 223: 0xffd7af, 317 | 224: 0xffd7d7, 318 | 225: 0xffd7ff, 319 | 226: 0xffff00, 320 | 227: 0xffff5f, 321 | 228: 0xffff87, 322 | 229: 0xffffaf, 323 | 230: 0xffffd7, 324 | 231: 0xffffff, 325 | 232: 0x080808, 326 | 233: 0x121212, 327 | 234: 0x1c1c1c, 328 | 235: 0x262626, 329 | 236: 0x303030, 330 | 237: 0x3a3a3a, 331 | 238: 0x444444, 332 | 239: 0x4e4e4e, 333 | 240: 0x585858, 334 | 241: 0x626262, 335 | 242: 0x6c6c6c, 336 | 243: 0x767676, 337 | 244: 0x808080, 338 | 245: 0x8a8a8a, 339 | 246: 0x949494, 340 | 247: 0x9e9e9e, 341 | 248: 0xa8a8a8, 342 | 249: 0xb2b2b2, 343 | 250: 0xbcbcbc, 344 | 251: 0xc6c6c6, 345 | 252: 0xd0d0d0, 346 | 253: 0xdadada, 347 | 254: 0xe4e4e4, 348 | 255: 0xeeeeee, 349 | } 350 | 351 | func (w *Writer) Write(data []byte) (n int, err error) { 352 | var csbi consoleScreenBufferInfo 353 | procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) 354 | 355 | er := bytes.NewBuffer(data) 356 | loop: 357 | for { 358 | r1, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) 359 | if r1 == 0 { 360 | break loop 361 | } 362 | 363 | c1, _, err := er.ReadRune() 364 | if err != nil { 365 | break loop 366 | } 367 | if c1 != 0x1b { 368 | fmt.Fprint(w.out, string(c1)) 369 | continue 370 | } 371 | c2, _, err := er.ReadRune() 372 | if err != nil { 373 | w.lastbuf.WriteRune(c1) 374 | break loop 375 | } 376 | if c2 != 0x5b { 377 | w.lastbuf.WriteRune(c1) 378 | w.lastbuf.WriteRune(c2) 379 | continue 380 | } 381 | 382 | var buf bytes.Buffer 383 | var m rune 384 | for { 385 | c, _, err := er.ReadRune() 386 | if err != nil { 387 | w.lastbuf.WriteRune(c1) 388 | w.lastbuf.WriteRune(c2) 389 | w.lastbuf.Write(buf.Bytes()) 390 | break loop 391 | } 392 | if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' { 393 | m = c 394 | break 395 | } 396 | buf.Write([]byte(string(c))) 397 | } 398 | 399 | var csbi consoleScreenBufferInfo 400 | switch m { 401 | case 'A': 402 | n, err = strconv.Atoi(buf.String()) 403 | if err != nil { 404 | continue 405 | } 406 | procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) 407 | csbi.cursorPosition.y -= short(n) 408 | procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) 409 | case 'B': 410 | n, err = strconv.Atoi(buf.String()) 411 | if err != nil { 412 | continue 413 | } 414 | procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) 415 | csbi.cursorPosition.y += short(n) 416 | procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) 417 | case 'C': 418 | n, err = strconv.Atoi(buf.String()) 419 | if err != nil { 420 | continue 421 | } 422 | procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) 423 | csbi.cursorPosition.x -= short(n) 424 | procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) 425 | case 'D': 426 | n, err = strconv.Atoi(buf.String()) 427 | if err != nil { 428 | continue 429 | } 430 | if n, err = strconv.Atoi(buf.String()); err == nil { 431 | var csbi consoleScreenBufferInfo 432 | procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) 433 | csbi.cursorPosition.x += short(n) 434 | procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) 435 | } 436 | case 'E': 437 | n, err = strconv.Atoi(buf.String()) 438 | if err != nil { 439 | continue 440 | } 441 | procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) 442 | csbi.cursorPosition.x = 0 443 | csbi.cursorPosition.y += short(n) 444 | procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) 445 | case 'F': 446 | n, err = strconv.Atoi(buf.String()) 447 | if err != nil { 448 | continue 449 | } 450 | procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) 451 | csbi.cursorPosition.x = 0 452 | csbi.cursorPosition.y -= short(n) 453 | procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) 454 | case 'G': 455 | n, err = strconv.Atoi(buf.String()) 456 | if err != nil { 457 | continue 458 | } 459 | procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) 460 | csbi.cursorPosition.x = short(n) 461 | procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) 462 | case 'H': 463 | token := strings.Split(buf.String(), ";") 464 | if len(token) != 2 { 465 | continue 466 | } 467 | n1, err := strconv.Atoi(token[0]) 468 | if err != nil { 469 | continue 470 | } 471 | n2, err := strconv.Atoi(token[1]) 472 | if err != nil { 473 | continue 474 | } 475 | csbi.cursorPosition.x = short(n2) 476 | csbi.cursorPosition.x = short(n1) 477 | procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) 478 | case 'J': 479 | n, err := strconv.Atoi(buf.String()) 480 | if err != nil { 481 | continue 482 | } 483 | var cursor coord 484 | switch n { 485 | case 0: 486 | cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y} 487 | case 1: 488 | cursor = coord{x: csbi.window.left, y: csbi.window.top} 489 | case 2: 490 | cursor = coord{x: csbi.window.left, y: csbi.window.top} 491 | } 492 | var count, written dword 493 | count = dword(csbi.size.x - csbi.cursorPosition.x + (csbi.size.y-csbi.cursorPosition.y)*csbi.size.x) 494 | procFillConsoleOutputCharacter.Call(uintptr(w.handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) 495 | procFillConsoleOutputAttribute.Call(uintptr(w.handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) 496 | case 'K': 497 | n, err := strconv.Atoi(buf.String()) 498 | if err != nil { 499 | continue 500 | } 501 | var cursor coord 502 | switch n { 503 | case 0: 504 | cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y} 505 | case 1: 506 | cursor = coord{x: csbi.window.left, y: csbi.window.top + csbi.cursorPosition.y} 507 | case 2: 508 | cursor = coord{x: csbi.window.left, y: csbi.window.top + csbi.cursorPosition.y} 509 | } 510 | var count, written dword 511 | count = dword(csbi.size.x - csbi.cursorPosition.x) 512 | procFillConsoleOutputCharacter.Call(uintptr(w.handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) 513 | procFillConsoleOutputAttribute.Call(uintptr(w.handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) 514 | case 'm': 515 | attr := csbi.attributes 516 | cs := buf.String() 517 | if cs == "" { 518 | procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(w.oldattr)) 519 | continue 520 | } 521 | token := strings.Split(cs, ";") 522 | for i := 0; i < len(token); i += 1 { 523 | ns := token[i] 524 | if n, err = strconv.Atoi(ns); err == nil { 525 | switch { 526 | case n == 0 || n == 100: 527 | attr = w.oldattr 528 | case 1 <= n && n <= 5: 529 | attr |= foregroundIntensity 530 | case n == 7: 531 | attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4) 532 | case 22 == n || n == 25 || n == 25: 533 | attr |= foregroundIntensity 534 | case n == 27: 535 | attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4) 536 | case 30 <= n && n <= 37: 537 | attr = (attr & backgroundMask) 538 | if (n-30)&1 != 0 { 539 | attr |= foregroundRed 540 | } 541 | if (n-30)&2 != 0 { 542 | attr |= foregroundGreen 543 | } 544 | if (n-30)&4 != 0 { 545 | attr |= foregroundBlue 546 | } 547 | case n == 38: // set foreground color. 548 | if i < len(token)-2 && (token[i+1] == "5" || token[i+1] == "05") { 549 | if n256, err := strconv.Atoi(token[i+2]); err == nil { 550 | if n256foreAttr == nil { 551 | n256setup() 552 | } 553 | attr &= backgroundMask 554 | attr |= n256foreAttr[n256] 555 | i += 2 556 | } 557 | } else { 558 | attr = attr & (w.oldattr & backgroundMask) 559 | } 560 | case n == 39: // reset foreground color. 561 | attr &= backgroundMask 562 | attr |= w.oldattr & foregroundMask 563 | case 40 <= n && n <= 47: 564 | attr = (attr & foregroundMask) 565 | if (n-40)&1 != 0 { 566 | attr |= backgroundRed 567 | } 568 | if (n-40)&2 != 0 { 569 | attr |= backgroundGreen 570 | } 571 | if (n-40)&4 != 0 { 572 | attr |= backgroundBlue 573 | } 574 | case n == 48: // set background color. 575 | if i < len(token)-2 && token[i+1] == "5" { 576 | if n256, err := strconv.Atoi(token[i+2]); err == nil { 577 | if n256backAttr == nil { 578 | n256setup() 579 | } 580 | attr &= foregroundMask 581 | attr |= n256backAttr[n256] 582 | i += 2 583 | } 584 | } else { 585 | attr = attr & (w.oldattr & foregroundMask) 586 | } 587 | case n == 49: // reset foreground color. 588 | attr &= foregroundMask 589 | attr |= w.oldattr & backgroundMask 590 | case 90 <= n && n <= 97: 591 | attr = (attr & backgroundMask) 592 | attr |= foregroundIntensity 593 | if (n-90)&1 != 0 { 594 | attr |= foregroundRed 595 | } 596 | if (n-90)&2 != 0 { 597 | attr |= foregroundGreen 598 | } 599 | if (n-90)&4 != 0 { 600 | attr |= foregroundBlue 601 | } 602 | case 100 <= n && n <= 107: 603 | attr = (attr & foregroundMask) 604 | attr |= backgroundIntensity 605 | if (n-100)&1 != 0 { 606 | attr |= backgroundRed 607 | } 608 | if (n-100)&2 != 0 { 609 | attr |= backgroundGreen 610 | } 611 | if (n-100)&4 != 0 { 612 | attr |= backgroundBlue 613 | } 614 | } 615 | procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(attr)) 616 | } 617 | } 618 | } 619 | } 620 | return len(data) - w.lastbuf.Len(), nil 621 | } 622 | 623 | type consoleColor struct { 624 | rgb int 625 | red bool 626 | green bool 627 | blue bool 628 | intensity bool 629 | } 630 | 631 | func (c consoleColor) foregroundAttr() (attr word) { 632 | if c.red { 633 | attr |= foregroundRed 634 | } 635 | if c.green { 636 | attr |= foregroundGreen 637 | } 638 | if c.blue { 639 | attr |= foregroundBlue 640 | } 641 | if c.intensity { 642 | attr |= foregroundIntensity 643 | } 644 | return 645 | } 646 | 647 | func (c consoleColor) backgroundAttr() (attr word) { 648 | if c.red { 649 | attr |= backgroundRed 650 | } 651 | if c.green { 652 | attr |= backgroundGreen 653 | } 654 | if c.blue { 655 | attr |= backgroundBlue 656 | } 657 | if c.intensity { 658 | attr |= backgroundIntensity 659 | } 660 | return 661 | } 662 | 663 | var color16 = []consoleColor{ 664 | consoleColor{0x000000, false, false, false, false}, 665 | consoleColor{0x000080, false, false, true, false}, 666 | consoleColor{0x008000, false, true, false, false}, 667 | consoleColor{0x008080, false, true, true, false}, 668 | consoleColor{0x800000, true, false, false, false}, 669 | consoleColor{0x800080, true, false, true, false}, 670 | consoleColor{0x808000, true, true, false, false}, 671 | consoleColor{0xc0c0c0, true, true, true, false}, 672 | consoleColor{0x808080, false, false, false, true}, 673 | consoleColor{0x0000ff, false, false, true, true}, 674 | consoleColor{0x00ff00, false, true, false, true}, 675 | consoleColor{0x00ffff, false, true, true, true}, 676 | consoleColor{0xff0000, true, false, false, true}, 677 | consoleColor{0xff00ff, true, false, true, true}, 678 | consoleColor{0xffff00, true, true, false, true}, 679 | consoleColor{0xffffff, true, true, true, true}, 680 | } 681 | 682 | type hsv struct { 683 | h, s, v float32 684 | } 685 | 686 | func (a hsv) dist(b hsv) float32 { 687 | dh := a.h - b.h 688 | switch { 689 | case dh > 0.5: 690 | dh = 1 - dh 691 | case dh < -0.5: 692 | dh = -1 - dh 693 | } 694 | ds := a.s - b.s 695 | dv := a.v - b.v 696 | return float32(math.Sqrt(float64(dh*dh + ds*ds + dv*dv))) 697 | } 698 | 699 | func toHSV(rgb int) hsv { 700 | r, g, b := float32((rgb&0xFF0000)>>16)/256.0, 701 | float32((rgb&0x00FF00)>>8)/256.0, 702 | float32(rgb&0x0000FF)/256.0 703 | min, max := minmax3f(r, g, b) 704 | h := max - min 705 | if h > 0 { 706 | if max == r { 707 | h = (g - b) / h 708 | if h < 0 { 709 | h += 6 710 | } 711 | } else if max == g { 712 | h = 2 + (b-r)/h 713 | } else { 714 | h = 4 + (r-g)/h 715 | } 716 | } 717 | h /= 6.0 718 | s := max - min 719 | if max != 0 { 720 | s /= max 721 | } 722 | v := max 723 | return hsv{h: h, s: s, v: v} 724 | } 725 | 726 | type hsvTable []hsv 727 | 728 | func toHSVTable(rgbTable []consoleColor) hsvTable { 729 | t := make(hsvTable, len(rgbTable)) 730 | for i, c := range rgbTable { 731 | t[i] = toHSV(c.rgb) 732 | } 733 | return t 734 | } 735 | 736 | func (t hsvTable) find(rgb int) consoleColor { 737 | hsv := toHSV(rgb) 738 | n := 7 739 | l := float32(5.0) 740 | for i, p := range t { 741 | d := hsv.dist(p) 742 | if d < l { 743 | l, n = d, i 744 | } 745 | } 746 | return color16[n] 747 | } 748 | 749 | func minmax3f(a, b, c float32) (min, max float32) { 750 | if a < b { 751 | if b < c { 752 | return a, c 753 | } else if a < c { 754 | return a, b 755 | } else { 756 | return c, b 757 | } 758 | } else { 759 | if a < c { 760 | return b, c 761 | } else if b < c { 762 | return b, a 763 | } else { 764 | return c, a 765 | } 766 | } 767 | } 768 | 769 | var n256foreAttr []word 770 | var n256backAttr []word 771 | 772 | func n256setup() { 773 | n256foreAttr = make([]word, 256) 774 | n256backAttr = make([]word, 256) 775 | t := toHSVTable(color16) 776 | for i, rgb := range color256 { 777 | c := t.find(rgb) 778 | n256foreAttr[i] = c.foregroundAttr() 779 | n256backAttr[i] = c.backgroundAttr() 780 | } 781 | } 782 | -------------------------------------------------------------------------------- /color/isatty_appengine.go: -------------------------------------------------------------------------------- 1 | // +build appengine 2 | 3 | package color 4 | 5 | // IsTerminal returns true if the file descriptor is terminal which 6 | // is always false on on appengine classic which is a sandboxed PaaS. 7 | func IsTerminal(fd uintptr) bool { 8 | return false 9 | } 10 | -------------------------------------------------------------------------------- /color/isatty_bsd.go: -------------------------------------------------------------------------------- 1 | // +build darwin freebsd openbsd netbsd 2 | // +build !appengine 3 | 4 | package color 5 | 6 | import ( 7 | "syscall" 8 | "unsafe" 9 | ) 10 | 11 | const ioctlReadTermios = syscall.TIOCGETA 12 | 13 | // IsTerminal return true if the file descriptor is terminal. 14 | func IsTerminal(fd uintptr) bool { 15 | var termios syscall.Termios 16 | _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) 17 | return err == 0 18 | } 19 | -------------------------------------------------------------------------------- /color/isatty_linux.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | // +build !appengine 3 | 4 | package color 5 | 6 | import ( 7 | "syscall" 8 | "unsafe" 9 | ) 10 | 11 | const ioctlReadTermios = syscall.TCGETS 12 | 13 | // IsTerminal return true if the file descriptor is terminal. 14 | func IsTerminal(fd uintptr) bool { 15 | var termios syscall.Termios 16 | _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) 17 | return err == 0 18 | } 19 | -------------------------------------------------------------------------------- /color/isatty_solaris.go: -------------------------------------------------------------------------------- 1 | // +build solaris 2 | // +build !appengine 3 | 4 | package color 5 | 6 | import ( 7 | "golang.org/x/sys/unix" 8 | ) 9 | 10 | // IsTerminal returns true if the given file descriptor is a terminal. 11 | // see: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libbc/libc/gen/common/isatty.c 12 | func IsTerminal(fd uintptr) bool { 13 | var termio unix.Termio 14 | err := unix.IoctlSetTermio(int(fd), unix.TCGETA, &termio) 15 | return err == nil 16 | } 17 | -------------------------------------------------------------------------------- /color/isatty_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | // +build !appengine 3 | 4 | package color 5 | 6 | import ( 7 | "syscall" 8 | "unsafe" 9 | ) 10 | 11 | var kernel32 = syscall.NewLazyDLL("kernel32.dll") 12 | var procGetConsoleMode = kernel32.NewProc("GetConsoleMode") 13 | 14 | // IsTerminal return true if the file descriptor is terminal. 15 | func IsTerminal(fd uintptr) bool { 16 | var st uint32 17 | r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0) 18 | return r != 0 && e == 0 19 | } 20 | -------------------------------------------------------------------------------- /console.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Örjan Persson. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package logging 6 | 7 | import ( 8 | "fmt" 9 | "io" 10 | "log" 11 | "os" 12 | ) 13 | 14 | type color int 15 | 16 | // color values 17 | const ( 18 | ColorBlack = iota + 30 19 | ColorRed 20 | ColorGreen 21 | ColorYellow 22 | ColorBlue 23 | ColorMagenta 24 | ColorCyan 25 | ColorWhite 26 | ) 27 | 28 | // background color values 29 | const ( 30 | ColorBlackBg = iota + 40 31 | ColorRedBg 32 | ColorGreenBg 33 | ColorYellowBg 34 | ColorBlueBg 35 | ColorMagentaBg 36 | ColorCyanBg 37 | ColorWhiteBg 38 | ) 39 | 40 | var ( 41 | colors = []string{ 42 | CRITICAL: ColorSeq(ColorMagenta), 43 | ERROR: ColorSeq(ColorRed), 44 | WARNING: ColorSeq(ColorYellow), 45 | NOTICE: ColorSeq(ColorGreen), 46 | INFO: ColorSeq(ColorGreen), 47 | DEBUG: ColorSeq(ColorCyan), 48 | TRACE: ColorSeq(ColorCyan), 49 | } 50 | bgcolors = []string{ 51 | CRITICAL: ColorSeq(ColorMagentaBg), 52 | ERROR: ColorSeq(ColorRedBg), 53 | WARNING: ColorSeq(ColorYellowBg), 54 | NOTICE: ColorSeq(ColorGreenBg), 55 | INFO: ColorSeq(ColorGreenBg), 56 | DEBUG: ColorSeq(ColorCyanBg), 57 | TRACE: ColorSeq(ColorCyanBg), 58 | } 59 | boldcolors = []string{ 60 | CRITICAL: ColorSeqBold(ColorMagenta), 61 | ERROR: ColorSeqBold(ColorRed), 62 | WARNING: ColorSeqBold(ColorYellow), 63 | NOTICE: ColorSeqBold(ColorGreen), 64 | INFO: ColorSeqBold(ColorGreen), 65 | DEBUG: ColorSeqBold(ColorCyan), 66 | TRACE: ColorSeqBold(ColorCyan), 67 | } 68 | ) 69 | 70 | // LogBackend utilizes the standard log module. 71 | type LogBackend struct { 72 | Logger *log.Logger 73 | ErrLogger *log.Logger 74 | Color bool 75 | } 76 | 77 | // NewLogBackend creates a new LogBackend. 78 | func NewLogBackend(out io.Writer, prefix string, flag int, errOut ...io.Writer) *LogBackend { 79 | b := &LogBackend{Logger: log.New(out, prefix, flag)} 80 | if len(errOut) > 0 { 81 | b.ErrLogger = log.New(errOut[0], prefix, flag) 82 | } 83 | return b 84 | } 85 | 86 | // Log implements the Backend interface. 87 | func (b *LogBackend) Log(calldepth int, rec *Record) { 88 | var msg string 89 | if b.Color { 90 | msg = rec.Formatted(calldepth+1, true) 91 | } else { 92 | msg = rec.Formatted(calldepth+1, false) 93 | } 94 | var err error 95 | if rec.Level > ERROR || b.ErrLogger == nil || rec.Level == PRINT { 96 | err = b.Logger.Output(calldepth+2, msg) 97 | } else { 98 | err = b.ErrLogger.Output(calldepth+2, msg) 99 | } 100 | if err != nil { 101 | fmt.Fprintf(os.Stderr, "unable to Console Log msg:%s [error]%s\n", msg, err.Error()) 102 | } 103 | } 104 | 105 | // Close closes the log service. 106 | func (b *LogBackend) Close() {} 107 | 108 | // ConvertColors takes a list of ints representing colors for log levels and 109 | // converts them into strings for ANSI color formatting 110 | func ConvertColors(colors []int, bold bool) []string { 111 | converted := []string{} 112 | for _, i := range colors { 113 | if bold { 114 | converted = append(converted, ColorSeqBold(color(i))) 115 | } else { 116 | converted = append(converted, ColorSeq(color(i))) 117 | } 118 | } 119 | 120 | return converted 121 | } 122 | 123 | // ColorSeq adds color identifier 124 | func ColorSeq(color color) string { 125 | return fmt.Sprintf("\033[%dm", int(color)) 126 | } 127 | 128 | // ColorSeqBold adds blod color identifier 129 | func ColorSeqBold(color color) string { 130 | return fmt.Sprintf("\033[%d;1m", int(color)) 131 | } 132 | 133 | func doFmtVerbLevelColor(layout string, colorful bool, level Level, output io.Writer) { 134 | if colorful { 135 | switch layout { 136 | case "bold": 137 | output.Write([]byte(boldcolors[level])) 138 | case "bg": 139 | output.Write([]byte(bgcolors[level])) 140 | case "reset": 141 | output.Write([]byte("\033[0m")) 142 | default: 143 | output.Write([]byte(colors[level])) 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /console_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Örjan Persson. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package logging 6 | 7 | import ( 8 | "bytes" 9 | "io/ioutil" 10 | "log" 11 | "strings" 12 | "testing" 13 | ) 14 | 15 | func TestLogCalldepth(t *testing.T) { 16 | buf := &bytes.Buffer{} 17 | SetBackend(NewLogBackend(buf, "", log.Lshortfile)) 18 | SetFormatter(MustStringFormatter("%{shortfile} %{level} %{message}")) 19 | 20 | log := NewLogger("test") 21 | log.Info("test filename") 22 | 23 | parts := strings.SplitN(buf.String(), " ", 2) 24 | 25 | // Verify that the correct filename is registered by the stdlib logger 26 | if !strings.HasPrefix(parts[0], "console_test.go:") { 27 | t.Errorf("incorrect filename: %s", parts[0]) 28 | } 29 | // Verify that the correct filename is registered by go-logging 30 | if !strings.HasPrefix(parts[1], "console_test.go:") { 31 | t.Errorf("incorrect filename: %s", parts[1]) 32 | } 33 | } 34 | 35 | func c(log *Logger) { log.Info("test callpath") } 36 | func b(log *Logger) { c(log) } 37 | func a(log *Logger) { b(log) } 38 | 39 | func rec(log *Logger, r int) { 40 | if r == 0 { 41 | a(log) 42 | return 43 | } 44 | rec(log, r-1) 45 | } 46 | 47 | func testCallpath(t *testing.T, format string, expect string) { 48 | buf := &bytes.Buffer{} 49 | SetBackend(NewLogBackend(buf, "", log.Lshortfile)) 50 | SetFormatter(MustStringFormatter(format)) 51 | 52 | logger := NewLogger("test") 53 | rec(logger, 6) 54 | 55 | parts := strings.SplitN(buf.String(), " ", 3) 56 | 57 | // Verify that the correct filename is registered by the stdlib logger 58 | if !strings.HasPrefix(parts[0], "console_test.go:") { 59 | t.Errorf("incorrect filename: %s", parts[0]) 60 | } 61 | // Verify that the correct callpath is registered by go-logging 62 | if !strings.HasPrefix(parts[1], expect) { 63 | t.Errorf("incorrect callpath: %s", parts[1]) 64 | } 65 | // Verify that the correct message is registered by go-logging 66 | if !strings.HasPrefix(parts[2], "test callpath") { 67 | t.Errorf("incorrect message: %s", parts[2]) 68 | } 69 | } 70 | 71 | func TestLogCallpath(t *testing.T) { 72 | testCallpath(t, "%{callpath} %{message}", "TestLogCallpath.testCallpath.rec...rec.a.b.c") 73 | testCallpath(t, "%{callpath:-1} %{message}", "TestLogCallpath.testCallpath.rec...rec.a.b.c") 74 | testCallpath(t, "%{callpath:0} %{message}", "TestLogCallpath.testCallpath.rec...rec.a.b.c") 75 | testCallpath(t, "%{callpath:1} %{message}", "~.c") 76 | testCallpath(t, "%{callpath:2} %{message}", "~.b.c") 77 | testCallpath(t, "%{callpath:3} %{message}", "~.a.b.c") 78 | } 79 | 80 | func BenchmarkLogMemoryBackendIgnored(b *testing.B) { 81 | backend := SetBackend(NewMemoryBackend(1024)) 82 | backend.SetLevel(INFO, "") 83 | RunLogBenchmark(b) 84 | } 85 | 86 | func BenchmarkLogMemoryBackend(b *testing.B) { 87 | backend := SetBackend(NewMemoryBackend(1024)) 88 | backend.SetLevel(DEBUG, "") 89 | RunLogBenchmark(b) 90 | } 91 | 92 | func BenchmarkLogChannelMemoryBackend(b *testing.B) { 93 | channelBackend := NewChannelMemoryBackend(1024) 94 | backend := SetBackend(channelBackend) 95 | backend.SetLevel(DEBUG, "") 96 | RunLogBenchmark(b) 97 | channelBackend.Flush() 98 | } 99 | 100 | func BenchmarkLogLeveled(b *testing.B) { 101 | backend := SetBackend(NewLogBackend(ioutil.Discard, "", 0)) 102 | backend.SetLevel(INFO, "") 103 | 104 | RunLogBenchmark(b) 105 | } 106 | 107 | func BenchmarkLogLogBackend(b *testing.B) { 108 | backend := SetBackend(NewLogBackend(ioutil.Discard, "", 0)) 109 | backend.SetLevel(DEBUG, "") 110 | RunLogBenchmark(b) 111 | } 112 | 113 | func BenchmarkLogLogBackendColor(b *testing.B) { 114 | colorizer := NewLogBackend(ioutil.Discard, "", 0) 115 | colorizer.Color = true 116 | backend := SetBackend(colorizer) 117 | backend.SetLevel(DEBUG, "") 118 | RunLogBenchmark(b) 119 | } 120 | 121 | func BenchmarkLogLogBackendStdFlags(b *testing.B) { 122 | backend := SetBackend(NewLogBackend(ioutil.Discard, "", log.LstdFlags)) 123 | backend.SetLevel(DEBUG, "") 124 | RunLogBenchmark(b) 125 | } 126 | 127 | func BenchmarkLogLogBackendLongFileFlag(b *testing.B) { 128 | backend := SetBackend(NewLogBackend(ioutil.Discard, "", log.Llongfile)) 129 | backend.SetLevel(DEBUG, "") 130 | RunLogBenchmark(b) 131 | } 132 | 133 | func RunLogBenchmark(b *testing.B) { 134 | password := Password("foo") 135 | log := NewLogger("test") 136 | 137 | b.ResetTimer() 138 | for i := 0; i < b.N; i++ { 139 | log.Debug("log line for %d and this is rectified: %s", i, password) 140 | } 141 | } 142 | 143 | func BenchmarkLogFixed(b *testing.B) { 144 | backend := SetBackend(NewLogBackend(ioutil.Discard, "", 0)) 145 | backend.SetLevel(DEBUG, "") 146 | 147 | RunLogBenchmarkFixedString(b) 148 | } 149 | 150 | func BenchmarkLogFixedIgnored(b *testing.B) { 151 | backend := SetBackend(NewLogBackend(ioutil.Discard, "", 0)) 152 | backend.SetLevel(INFO, "") 153 | RunLogBenchmarkFixedString(b) 154 | } 155 | 156 | func RunLogBenchmarkFixedString(b *testing.B) { 157 | log := NewLogger("test") 158 | 159 | b.ResetTimer() 160 | for i := 0; i < b.N; i++ { 161 | log.Debug("some random fixed text") 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestExample(t *testing.T) { 9 | // This call is for testing purposes and will set the time to unix epoch. 10 | InitForTesting(DEBUG) 11 | 12 | var log = NewLogger("example") 13 | 14 | // For demo purposes, create two backend for os.Stdout. 15 | // 16 | // os.Stderr should most likely be used in the real world but then the 17 | // "Output:" check in this example would not work. 18 | backend1 := NewLogBackend(os.Stdout, "", 0) 19 | backend2 := NewLogBackend(os.Stdout, "", 0) 20 | 21 | // For messages written to backend2 we want to add some additional 22 | // information to the output, including the used log level and the name of 23 | // the function. 24 | var format = MustStringFormatter( 25 | `%{time:2006/01/02 15:04:05.000000} %{shortfunc} %{level:.1s} %{message}`, 26 | ) 27 | backend2Formatter := NewBackendFormatter(backend2, format) 28 | 29 | // Only errors and more severe messages should be sent to backend2 30 | backend2Leveled := AddModuleLevel(backend2Formatter) 31 | backend2Leveled.SetLevel(ERROR, "") 32 | 33 | // Set the backends to be used and the default level. 34 | SetBackend(backend1, backend2Leveled) 35 | 36 | log.Debugf("debug %s", "arg") 37 | log.Error("error") 38 | 39 | // Output: 40 | // debug arg 41 | // error 42 | // 00:00:00.000 Example E error 43 | } 44 | -------------------------------------------------------------------------------- /examples/example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/henrylee2cn/go-logging" 7 | ) 8 | 9 | var log = logging.NewLogger("example") 10 | 11 | // Example format string. Everything except the message has a custom color 12 | // which is dependent on the log level. Many fields have a custom output 13 | // formatting too, eg. the time returns the hour down to the milli second. 14 | var format = logging.MustStringFormatter( 15 | `%{color:bold}%{module} %{time:15:04:05.000} %{longfile} ▶ %{level:.4s} %{id:03x}%{color:reset} %{message}`, 16 | ) 17 | 18 | // Password is just an example type implementing the Redactor interface. Any 19 | // time this is logged, the Redacted() function will be called. 20 | type Password string 21 | 22 | func (p Password) Redacted() interface{} { 23 | return logging.Redact(string(p)) 24 | } 25 | 26 | func main() { 27 | // For demo purposes, create two backend for os.Stderr. 28 | backend1 := logging.NewLogBackend(os.Stderr, "", 0) 29 | backend2 := logging.NewLogBackend(os.Stderr, "", 0) 30 | backend1.Color = true 31 | backend2.Color = true 32 | 33 | // For messages written to backend2 we want to add some additional 34 | // information to the output, including the used log level and the name of 35 | // the function. 36 | backend2Formatter := logging.NewBackendFormatter(backend2, format) 37 | 38 | // Only errors and more severe messages should be sent to backend1 39 | backend1Leveled := logging.AddModuleLevel(backend1) 40 | backend1Leveled.SetLevel(logging.ERROR, "") 41 | backend2Leveled := logging.AddModuleLevel(backend2Formatter) 42 | backend2Leveled.SetLevel(logging.TRACE, "") 43 | 44 | // Set the backends to be used. 45 | logging.SetBackend(logging.MultiLogger(backend1Leveled, backend2Leveled)) 46 | 47 | log.Tracef("tracef %s", Password("secret")) 48 | log.Debugf("debug %s", Password("secret")) 49 | log.Info("info") 50 | log.Notice("notice") 51 | log.Warning("warning") 52 | log.Error("err") 53 | log.Critical("crit") 54 | log.Printf("print") 55 | // log.Close() 56 | } 57 | -------------------------------------------------------------------------------- /examples/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andeya/go-logging/c02b68314b1a550f767fb341c00748bc562e9aa4/examples/example.png -------------------------------------------------------------------------------- /file.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "os" 9 | "path/filepath" 10 | "regexp" 11 | "strings" 12 | "sync" 13 | "time" 14 | ) 15 | 16 | // FileBackend implements LoggerInterface. 17 | // It writes messages by lines limit, file size limit, or time frequency. 18 | type FileBackend struct { 19 | sync.Mutex // write log order by order and atomic incr maxLinesCurLines and maxSizeCurSize 20 | statusLock sync.RWMutex 21 | status int8 // 0:close 1:run 22 | // The opened file 23 | Filename string `json:"filename"` 24 | fileWriter *os.File 25 | 26 | // Rotate at line 27 | MaxLines int `json:"maxlines"` 28 | maxLinesCurLines int 29 | 30 | // Rotate at size 31 | MaxSize int `json:"maxsize"` 32 | maxSizeCurSize int 33 | 34 | // Rotate daily 35 | Daily bool `json:"daily"` 36 | MaxDays int64 `json:"maxdays"` 37 | dailyOpenDate int 38 | 39 | Rotate bool `json:"rotate"` 40 | 41 | Perm os.FileMode `json:"perm"` 42 | 43 | fileNameOnly, suffix string // like "project.log", project is fileNameOnly and .log is suffix 44 | // Asynchronous output channels 45 | asyncMsgChan chan []byte 46 | asyncSignalChan chan struct{} 47 | } 48 | 49 | // NewDefaultFileBackend create a FileLogWriter returning as LoggerInterface. 50 | func NewDefaultFileBackend(filename string, asyncLen ...int) (*FileBackend, error) { 51 | if len(filename) == 0 { 52 | return nil, errors.New("FileBackend must have filename") 53 | } 54 | 55 | w := &FileBackend{ 56 | Filename: filename, 57 | MaxLines: 1000000, 58 | MaxSize: 1 << 28, //256 MB 59 | Daily: true, 60 | MaxDays: 7, 61 | Rotate: true, 62 | Perm: 0660, 63 | } 64 | if len(asyncLen) > 0 && asyncLen[0] > 0 { 65 | w.asyncMsgChan = make(chan []byte, asyncLen[0]) 66 | w.asyncSignalChan = make(chan struct{}) 67 | } 68 | 69 | w.suffix = filepath.Ext(w.Filename) 70 | w.fileNameOnly = strings.TrimSuffix(w.Filename, w.suffix) 71 | if w.suffix == "" { 72 | w.suffix = ".log" 73 | } 74 | p, _ := filepath.Split(w.Filename) 75 | d, err := os.Stat(p) 76 | if err != nil || !d.IsDir() { 77 | os.MkdirAll(p, 0777) 78 | } 79 | err = w.startLogger() 80 | return w, err 81 | } 82 | 83 | // start file logger. create log file and set to locker-inside file writer. 84 | func (w *FileBackend) startLogger() error { 85 | file, err := w.createLogFile() 86 | if err != nil { 87 | return err 88 | } 89 | if w.fileWriter != nil { 90 | w.fileWriter.Close() 91 | } 92 | w.fileWriter = file 93 | err = w.initFd() 94 | if err == nil { 95 | w.status = 1 96 | if w.asyncMsgChan != nil { 97 | go func() { 98 | for { 99 | select { 100 | case msg := <-w.asyncMsgChan: 101 | w.write(msg) 102 | case <-w.asyncSignalChan: 103 | return 104 | } 105 | } 106 | }() 107 | } 108 | } 109 | return err 110 | } 111 | 112 | func (w *FileBackend) needRotate(size int, day int) bool { 113 | return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) || 114 | (w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) || 115 | (w.Daily && day != w.dailyOpenDate) 116 | 117 | } 118 | 119 | var colorRegexp = regexp.MustCompile("\x1b\\[[0-9]{1,2}m") 120 | 121 | // Log implements the Backend interface. 122 | func (w *FileBackend) Log(calldepth int, rec *Record) { 123 | w.statusLock.RLock() 124 | if w.status == 0 { 125 | w.statusLock.RUnlock() 126 | return 127 | } 128 | msg := colorRegexp.ReplaceAll([]byte(rec.Formatted(calldepth+1, false)), []byte{}) 129 | if msg[len(msg)-1] != '\n' { 130 | msg = append(msg, '\n') 131 | } 132 | d := rec.Time.Day() 133 | if w.Rotate { 134 | if w.needRotate(len(msg), d) { 135 | w.Lock() 136 | if w.needRotate(len(msg), d) { 137 | if err := w.doRotate(rec.Time); err != nil { 138 | fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err) 139 | } 140 | } 141 | w.Unlock() 142 | } 143 | } 144 | if w.asyncMsgChan != nil { 145 | w.asyncMsgChan <- msg 146 | } else { 147 | w.write(msg) 148 | } 149 | w.statusLock.RUnlock() 150 | } 151 | 152 | // Close close the file description, close file writer. 153 | // Flush waits until all records in the buffered channel have been processed, 154 | // and flushs file logger. 155 | // there are no buffering messages in file logger in memory. 156 | // flush file means sync file from disk. 157 | func (w *FileBackend) Close() { 158 | w.statusLock.Lock() 159 | if w.status == 0 { 160 | w.statusLock.Unlock() 161 | return 162 | } 163 | w.status = 0 164 | w.statusLock.Unlock() 165 | if w.asyncSignalChan != nil { 166 | w.asyncSignalChan <- struct{}{} 167 | close(w.asyncSignalChan) 168 | close(w.asyncMsgChan) 169 | for msg := range w.asyncMsgChan { 170 | w.write(msg) 171 | } 172 | } 173 | w.fileWriter.Sync() 174 | w.fileWriter.Close() 175 | } 176 | 177 | func (w *FileBackend) write(msg []byte) { 178 | w.Lock() 179 | _, err := w.fileWriter.Write(msg) 180 | if err == nil { 181 | w.maxLinesCurLines++ 182 | w.maxSizeCurSize += len(msg) 183 | } 184 | w.Unlock() 185 | if err != nil { 186 | fmt.Fprintf(os.Stderr, "unable to File Log msg:%s [error]%s\n", msg, err.Error()) 187 | } 188 | } 189 | 190 | func (w *FileBackend) createLogFile() (*os.File, error) { 191 | // Open the log file 192 | fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, w.Perm) 193 | return fd, err 194 | } 195 | 196 | func (w *FileBackend) initFd() error { 197 | fd := w.fileWriter 198 | fInfo, err := fd.Stat() 199 | if err != nil { 200 | return fmt.Errorf("get stat err: %s\n", err) 201 | } 202 | w.maxSizeCurSize = int(fInfo.Size()) 203 | w.dailyOpenDate = time.Now().Day() 204 | w.maxLinesCurLines = 0 205 | if fInfo.Size() > 0 { 206 | count, err := w.lines() 207 | if err != nil { 208 | return err 209 | } 210 | w.maxLinesCurLines = count 211 | } 212 | return nil 213 | } 214 | 215 | func (w *FileBackend) lines() (int, error) { 216 | fd, err := os.Open(w.Filename) 217 | if err != nil { 218 | return 0, err 219 | } 220 | defer fd.Close() 221 | 222 | buf := make([]byte, 32768) // 32k 223 | count := 0 224 | lineSep := []byte{'\n'} 225 | 226 | for { 227 | c, err := fd.Read(buf) 228 | if err != nil && err != io.EOF { 229 | return count, err 230 | } 231 | 232 | count += bytes.Count(buf[:c], lineSep) 233 | 234 | if err == io.EOF { 235 | break 236 | } 237 | } 238 | 239 | return count, nil 240 | } 241 | 242 | // DoRotate means it need to write file in new file. 243 | // new file name like xx.2013-01-01.log (daily) or xx.001.log (by line or size) 244 | func (w *FileBackend) doRotate(logTime time.Time) error { 245 | _, err := os.Lstat(w.Filename) 246 | if err != nil { 247 | return err 248 | } 249 | // file exists 250 | // Find the next available number 251 | num := 1 252 | fName := "" 253 | if w.MaxLines > 0 || w.MaxSize > 0 { 254 | for ; err == nil && num <= 999; num++ { 255 | fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format("2006-01-02"), num, w.suffix) 256 | _, err = os.Lstat(fName) 257 | } 258 | } else { 259 | fName = fmt.Sprintf("%s.%s%s", w.fileNameOnly, logTime.Format("2006-01-02"), w.suffix) 260 | _, err = os.Lstat(fName) 261 | } 262 | // return error if the last file checked still existed 263 | if err == nil { 264 | return fmt.Errorf("Rotate: Cannot find free log number to rename %s\n", w.Filename) 265 | } 266 | 267 | // close fileWriter before rename 268 | w.fileWriter.Close() 269 | 270 | // Rename the file to its new found name 271 | // even if occurs error,we MUST guarantee to restart new logger 272 | renameErr := os.Rename(w.Filename, fName) 273 | // re-start logger 274 | startLoggerErr := w.startLogger() 275 | go w.deleteOldLog() 276 | 277 | if startLoggerErr != nil { 278 | return fmt.Errorf("Rotate StartLogger: %s\n", startLoggerErr) 279 | } 280 | if renameErr != nil { 281 | return fmt.Errorf("Rotate: %s\n", renameErr) 282 | } 283 | return nil 284 | 285 | } 286 | 287 | func (w *FileBackend) deleteOldLog() { 288 | dir := filepath.Dir(w.Filename) 289 | filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) { 290 | defer func() { 291 | if r := recover(); r != nil { 292 | fmt.Fprintf(os.Stderr, "Unable to delete old log '%s', error: %v\n", path, r) 293 | } 294 | }() 295 | 296 | if !info.IsDir() && info.ModTime().Unix() < (time.Now().Unix()-60*60*24*w.MaxDays) { 297 | if strings.HasPrefix(filepath.Base(path), w.fileNameOnly) && 298 | strings.HasSuffix(filepath.Base(path), w.suffix) { 299 | os.Remove(path) 300 | } 301 | } 302 | return 303 | }) 304 | } 305 | -------------------------------------------------------------------------------- /file_test.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "strconv" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | func TestFile1(t *testing.T) { 13 | log := NewLogger("TestFile1") 14 | fileBackend, err := NewDefaultFileBackend("test.log") 15 | if err != nil { 16 | panic(err) 17 | } 18 | var format = MustStringFormatter( 19 | `%{color}%{module} %{time:15:04:05.000} %{longfile} ▶ %{level:.4s} %{id:03x}%{color:reset} %{message}`, 20 | ) 21 | log.SetBackend(AddModuleLevel(NewBackendFormatter(fileBackend, format))) 22 | log.Debug("debug\n") 23 | log.Info("info\n") 24 | log.Notice("notice\n") 25 | log.Warning("warning\n") 26 | log.Error("error\n") 27 | log.Critical("critical\n") 28 | f, err := os.Open("test.log") 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | b := bufio.NewReader(f) 33 | lineNum := 0 34 | for { 35 | line, _, err := b.ReadLine() 36 | if err != nil { 37 | break 38 | } 39 | if len(line) > 0 { 40 | lineNum++ 41 | } 42 | } 43 | f.Close() 44 | fileBackend.Close() 45 | var expected = int(DEBUG) + 1 46 | if lineNum != expected { 47 | t.Fatal(lineNum, "not "+strconv.Itoa(expected)+" lines") 48 | } 49 | os.Remove("test.log") 50 | } 51 | 52 | func TestFile2(t *testing.T) { 53 | log := NewLogger("TestFile2") 54 | fileBackend, err := NewDefaultFileBackend("test2.log", 1000) 55 | if err != nil { 56 | panic(err) 57 | } 58 | var format = MustStringFormatter( 59 | `%{color}%{module} %{time:15:04:05.000} %{longfile} ▶ %{level:.4s} %{id:03x}%{color:reset} %{message}`, 60 | ) 61 | log.SetBackend(AddModuleLevel(NewBackendFormatter(fileBackend, format))) 62 | log.Debug("debug\n") 63 | log.Info("info\n") 64 | log.Notice("notice\n") 65 | log.Warning("warning\n") 66 | log.Error("error\n") 67 | log.Critical("critical\n") 68 | log.Close() 69 | f, err := os.Open("test2.log") 70 | if err != nil { 71 | t.Fatal(err) 72 | } 73 | b := bufio.NewReader(f) 74 | lineNum := 0 75 | for { 76 | line, _, err := b.ReadLine() 77 | if err != nil { 78 | break 79 | } 80 | if len(line) > 0 { 81 | lineNum++ 82 | } 83 | } 84 | f.Close() 85 | var expected = int(DEBUG) + 1 86 | if lineNum != expected { 87 | t.Fatal(lineNum, "not "+strconv.Itoa(expected)+" lines") 88 | } 89 | os.Remove("test2.log") 90 | } 91 | 92 | func TestFileRotate(t *testing.T) { 93 | log := NewLogger("TestFileRotate") 94 | fileBackend, err := NewDefaultFileBackend("test3.log") 95 | if err != nil { 96 | panic(err) 97 | } 98 | fileBackend.MaxLines = 4 99 | log.SetBackend(AddModuleLevel(fileBackend)) 100 | log.Debug("debug") 101 | log.Info("info") 102 | log.Notice("notice") 103 | log.Warn("warning") 104 | log.Error("error") 105 | log.Critical("critical") 106 | rotateName := "test3" + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), 1) + ".log" 107 | b, err := exists(rotateName) 108 | if !b || err != nil { 109 | os.Remove("test3.log") 110 | t.Fatal("rotate not generated") 111 | } 112 | fileBackend.Close() 113 | os.Remove(rotateName) 114 | os.Remove("test3.log") 115 | } 116 | 117 | func exists(path string) (bool, error) { 118 | _, err := os.Stat(path) 119 | if err == nil { 120 | return true, nil 121 | } 122 | if os.IsNotExist(err) { 123 | return false, nil 124 | } 125 | return false, err 126 | } 127 | 128 | func BenchmarkFile(b *testing.B) { 129 | log := NewLogger("BenchmarkFile") 130 | fileBackend, err := NewDefaultFileBackend("test4.log") 131 | if err != nil { 132 | panic(err) 133 | } 134 | log.SetBackend(AddModuleLevel(fileBackend)) 135 | for i := 0; i < b.N; i++ { 136 | log.Debug("debug") 137 | } 138 | fileBackend.Close() 139 | os.Remove("test4.log") 140 | } 141 | 142 | func BenchmarkFileAsynchronous(b *testing.B) { 143 | log := NewLogger("BenchmarkFileAsynchronous") 144 | fileBackend, err := NewDefaultFileBackend("test4.log") 145 | if err != nil { 146 | panic(err) 147 | } 148 | log.SetBackend(AddModuleLevel(fileBackend)) 149 | for i := 0; i < b.N; i++ { 150 | log.Debug("debug") 151 | } 152 | fileBackend.Close() 153 | os.Remove("test4.log") 154 | } 155 | 156 | func BenchmarkFileCallDepth(b *testing.B) { 157 | log := NewLogger("BenchmarkFileCallDepth") 158 | fileBackend, err := NewDefaultFileBackend("test4.log") 159 | if err != nil { 160 | panic(err) 161 | } 162 | log.SetBackend(AddModuleLevel(fileBackend)) 163 | log.ExtraCalldepth = 2 164 | for i := 0; i < b.N; i++ { 165 | log.Debug("debug") 166 | } 167 | fileBackend.Close() 168 | os.Remove("test4.log") 169 | } 170 | 171 | func BenchmarkFileAsynchronousCallDepth(b *testing.B) { 172 | log := NewLogger("BenchmarkFileAsynchronousCallDepth") 173 | fileBackend, err := NewDefaultFileBackend("test4.log") 174 | if err != nil { 175 | panic(err) 176 | } 177 | log.SetBackend(AddModuleLevel(fileBackend)) 178 | log.ExtraCalldepth = 2 179 | for i := 0; i < b.N; i++ { 180 | log.Debug("debug") 181 | } 182 | fileBackend.Close() 183 | os.Remove("test4.log") 184 | } 185 | 186 | func BenchmarkFileOnGoroutine(b *testing.B) { 187 | log := NewLogger("BenchmarkFileOnGoroutine") 188 | fileBackend, err := NewDefaultFileBackend("test4.log") 189 | if err != nil { 190 | panic(err) 191 | } 192 | log.SetBackend(AddModuleLevel(fileBackend)) 193 | for i := 0; i < b.N; i++ { 194 | go log.Debug("debug") 195 | } 196 | fileBackend.Close() 197 | os.Remove("test4.log") 198 | } 199 | -------------------------------------------------------------------------------- /format.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Örjan Persson. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package logging 6 | 7 | import ( 8 | "bytes" 9 | "errors" 10 | "fmt" 11 | "io" 12 | "os" 13 | "path" 14 | "path/filepath" 15 | "regexp" 16 | "runtime" 17 | "strconv" 18 | "strings" 19 | "sync" 20 | "time" 21 | ) 22 | 23 | // TODO see Formatter interface in fmt/print.go 24 | // TODO try text/template, maybe it have enough performance 25 | // TODO other template systems? 26 | // TODO make it possible to specify formats per backend? 27 | type fmtVerb int 28 | 29 | const ( 30 | fmtVerbTime fmtVerb = iota 31 | fmtVerbLevel 32 | fmtVerbID 33 | fmtVerbPid 34 | fmtVerbProgram 35 | fmtVerbModule 36 | fmtVerbMessage 37 | fmtVerbLongfile 38 | fmtVerbShortfile 39 | fmtVerbLongpkg 40 | fmtVerbShortpkg 41 | fmtVerbLongfunc 42 | fmtVerbShortfunc 43 | fmtVerbCallpath 44 | fmtVerbLevelColor 45 | 46 | // Keep last, there are no match for these below. 47 | fmtVerbUnknown 48 | fmtVerbStatic 49 | ) 50 | 51 | var fmtVerbs = []string{ 52 | "time", 53 | "level", 54 | "id", 55 | "pid", 56 | "program", 57 | "module", 58 | "message", 59 | "longfile", 60 | "shortfile", 61 | "longpkg", 62 | "shortpkg", 63 | "longfunc", 64 | "shortfunc", 65 | "callpath", 66 | "color", 67 | } 68 | 69 | const rfc3339Milli = "2006-01-02T15:04:05.999Z07:00" 70 | 71 | var defaultVerbsLayout = []string{ 72 | rfc3339Milli, 73 | "s", 74 | "d", 75 | "d", 76 | "s", 77 | "s", 78 | "s", 79 | "s", 80 | "s", 81 | "s", 82 | "s", 83 | "s", 84 | "s", 85 | "0", 86 | "", 87 | } 88 | 89 | var ( 90 | pid = os.Getpid() 91 | program = filepath.Base(os.Args[0]) 92 | ) 93 | 94 | func getFmtVerbByName(name string) fmtVerb { 95 | for i, verb := range fmtVerbs { 96 | if name == verb { 97 | return fmtVerb(i) 98 | } 99 | } 100 | return fmtVerbUnknown 101 | } 102 | 103 | // Formatter is the required interface for a custom log record formatter. 104 | type Formatter interface { 105 | Format(calldepth int, colorful bool, r *Record, w io.Writer) error 106 | } 107 | 108 | // formatter is used by all backends unless otherwise overridden. 109 | var formatter struct { 110 | sync.RWMutex 111 | def Formatter 112 | } 113 | 114 | func getFormatter() Formatter { 115 | formatter.RLock() 116 | defer formatter.RUnlock() 117 | return formatter.def 118 | } 119 | 120 | var ( 121 | // DefaultFormatter is the default formatter used and is only the message. 122 | DefaultFormatter = MustStringFormatter("%{message}") 123 | 124 | // GlogFormatter mimics the glog format 125 | GlogFormatter = MustStringFormatter("%{level:.1s}%{time:0102 15:04:05.999999} %{pid} %{shortfile}] %{message}") 126 | ) 127 | 128 | // SetFormatter sets the default formatter for all new backends. A backend will 129 | // fetch this value once it is needed to format a record. Note that backends 130 | // will cache the formatter after the first point. For now, make sure to set 131 | // the formatter before logging. 132 | func SetFormatter(f Formatter) { 133 | formatter.Lock() 134 | defer formatter.Unlock() 135 | formatter.def = f 136 | } 137 | 138 | var formatRe = regexp.MustCompile(`%{([a-z]+)(?::(.*?[^\\]))?}`) 139 | 140 | type part struct { 141 | verb fmtVerb 142 | layout string 143 | } 144 | 145 | // stringFormatter contains a list of parts which explains how to build the 146 | // formatted string passed on to the logging backend. 147 | type stringFormatter struct { 148 | parts []part 149 | } 150 | 151 | // NewStringFormatter returns a new Formatter which outputs the log record as a 152 | // string based on the 'verbs' specified in the format string. 153 | // 154 | // The verbs: 155 | // 156 | // General: 157 | // %{id} Sequence number for log message (uint64). 158 | // %{pid} Process id (int) 159 | // %{time} Time when log occurred (time.Time) 160 | // %{level} Log level (Level) 161 | // %{module} Module (string) 162 | // %{program} Basename of os.Args[0] (string) 163 | // %{message} Message (string) 164 | // %{longfile} Full file name and line number: /a/b/c/d.go:23 165 | // %{shortfile} Final file name element and line number: d.go:23 166 | // %{callpath} Callpath like main.a.b.c...c "..." meaning recursive call ~. meaning truncated path 167 | // %{color} ANSI color based on log level 168 | // 169 | // For normal types, the output can be customized by using the 'verbs' defined 170 | // in the fmt package, eg. '%{id:04d}' to make the id output be '%04d' as the 171 | // format string. 172 | // 173 | // For time.Time, use the same layout as time.Format to change the time format 174 | // when output, eg "2006-01-02T15:04:05.999Z-07:00". 175 | // 176 | // For the 'color' verb, the output can be adjusted to either use bold colors, 177 | // i.e., '%{color:bold}' or to reset the ANSI attributes, i.e., 178 | // '%{color:reset}' Note that if you use the color verb explicitly, be sure to 179 | // reset it or else the color state will persist past your log message. e.g., 180 | // "%{color:bold}%{time:15:04:05} %{level:-8s}%{color:reset} %{message}" will 181 | // just colorize the time and level, leaving the message uncolored. 182 | // 183 | // For the 'callpath' verb, the output can be adjusted to limit the printing 184 | // the stack depth. i.e. '%{callpath:3}' will print '~.a.b.c' 185 | // 186 | // Colors on Windows is unfortunately not supported right now and is currently 187 | // a no-op. 188 | // 189 | // There's also a couple of experimental 'verbs'. These are exposed to get 190 | // feedback and needs a bit of tinkering. Hence, they might change in the 191 | // future. 192 | // 193 | // Experimental: 194 | // %{longpkg} Full package path, eg. github.com/go-logging 195 | // %{shortpkg} Base package path, eg. go-logging 196 | // %{longfunc} Full function name, eg. littleEndian.PutUint32 197 | // %{shortfunc} Base function name, eg. PutUint32 198 | // %{callpath} Call function path, eg. main.a.b.c 199 | func NewStringFormatter(format string) (Formatter, error) { 200 | var fmter = &stringFormatter{} 201 | 202 | // Find the boundaries of all %{vars} 203 | matches := formatRe.FindAllStringSubmatchIndex(format, -1) 204 | if matches == nil { 205 | return nil, errors.New("logger: invalid log format: " + format) 206 | } 207 | 208 | // Collect all variables and static text for the format 209 | prev := 0 210 | for _, m := range matches { 211 | start, end := m[0], m[1] 212 | if start > prev { 213 | fmter.add(fmtVerbStatic, format[prev:start]) 214 | } 215 | 216 | name := format[m[2]:m[3]] 217 | verb := getFmtVerbByName(name) 218 | if verb == fmtVerbUnknown { 219 | return nil, errors.New("logger: unknown variable: " + name) 220 | } 221 | 222 | // Handle layout customizations or use the default. If this is not for the 223 | // time, color formatting or callpath, we need to prefix with %. 224 | layout := defaultVerbsLayout[verb] 225 | if m[4] != -1 { 226 | layout = format[m[4]:m[5]] 227 | } 228 | if verb != fmtVerbTime && verb != fmtVerbLevelColor && verb != fmtVerbCallpath { 229 | layout = "%" + layout 230 | } 231 | 232 | fmter.add(verb, layout) 233 | prev = end 234 | } 235 | end := format[prev:] 236 | if end != "" { 237 | fmter.add(fmtVerbStatic, end) 238 | } 239 | 240 | // Make a test run to make sure we can format it correctly. 241 | t, err := time.Parse(time.RFC3339, "2010-02-04T21:00:57-08:00") 242 | if err != nil { 243 | panic(err) 244 | } 245 | testFmt := "hello %s" 246 | r := &Record{ 247 | ID: 12345, 248 | Time: t, 249 | Module: "logger", 250 | Args: []interface{}{"go"}, 251 | fmt: &testFmt, 252 | } 253 | if err := fmter.Format(0, false, r, &bytes.Buffer{}); err != nil { 254 | return nil, err 255 | } 256 | 257 | return fmter, nil 258 | } 259 | 260 | // MustStringFormatter is equivalent to NewStringFormatter with a call to panic 261 | // on error. 262 | func MustStringFormatter(format string) Formatter { 263 | f, err := NewStringFormatter(format) 264 | if err != nil { 265 | panic("Failed to initialized string formatter: " + err.Error()) 266 | } 267 | return f 268 | } 269 | 270 | func (f *stringFormatter) add(verb fmtVerb, layout string) { 271 | f.parts = append(f.parts, part{verb, layout}) 272 | } 273 | 274 | func (f *stringFormatter) Format(calldepth int, colorful bool, r *Record, output io.Writer) error { 275 | for _, part := range f.parts { 276 | if part.verb == fmtVerbStatic { 277 | output.Write([]byte(part.layout)) 278 | } else if part.verb == fmtVerbTime { 279 | output.Write([]byte(r.Time.Format(part.layout))) 280 | } else if part.verb == fmtVerbLevelColor { 281 | doFmtVerbLevelColor(part.layout, colorful, r.Level, output) 282 | } else if part.verb == fmtVerbCallpath { 283 | depth, err := strconv.Atoi(part.layout) 284 | if err != nil { 285 | depth = 0 286 | } 287 | output.Write([]byte(formatCallpath(calldepth+1, depth))) 288 | } else { 289 | var v interface{} 290 | switch part.verb { 291 | case fmtVerbLevel: 292 | v = r.Level 293 | break 294 | case fmtVerbID: 295 | v = r.ID 296 | break 297 | case fmtVerbPid: 298 | v = pid 299 | break 300 | case fmtVerbProgram: 301 | v = program 302 | break 303 | case fmtVerbModule: 304 | v = r.Module 305 | break 306 | case fmtVerbMessage: 307 | v = r.Message() 308 | break 309 | case fmtVerbLongfile, fmtVerbShortfile: 310 | _, file, line, ok := runtime.Caller(calldepth + 1) 311 | if !ok { 312 | file = "???" 313 | line = 0 314 | } 315 | if part.verb == fmtVerbShortfile { 316 | file = filepath.Base(file) 317 | } else if idx := strings.Index(file, "/src/"); idx >= 0 { 318 | file = file[idx+5:] 319 | } 320 | v = fmt.Sprintf("%s:%d", file, line) 321 | case fmtVerbLongfunc, fmtVerbShortfunc, 322 | fmtVerbLongpkg, fmtVerbShortpkg: 323 | // TODO cache pc 324 | v = "???" 325 | if pc, _, _, ok := runtime.Caller(calldepth + 1); ok { 326 | if f := runtime.FuncForPC(pc); f != nil { 327 | v = formatFuncName(part.verb, f.Name()) 328 | } 329 | } 330 | default: 331 | panic("unhandled format part") 332 | } 333 | fmt.Fprintf(output, part.layout, v) 334 | } 335 | } 336 | return nil 337 | } 338 | 339 | // formatFuncName tries to extract certain part of the runtime formatted 340 | // function name to some pre-defined variation. 341 | // 342 | // This function is known to not work properly if the package path or name 343 | // contains a dot. 344 | func formatFuncName(v fmtVerb, f string) string { 345 | i := strings.LastIndex(f, "/") 346 | j := strings.Index(f[i+1:], ".") 347 | if j < 1 { 348 | return "???" 349 | } 350 | pkg, fun := f[:i+j+1], f[i+j+2:] 351 | switch v { 352 | case fmtVerbLongpkg: 353 | return pkg 354 | case fmtVerbShortpkg: 355 | return path.Base(pkg) 356 | case fmtVerbLongfunc: 357 | return fun 358 | case fmtVerbShortfunc: 359 | i = strings.LastIndex(fun, ".") 360 | return fun[i+1:] 361 | } 362 | panic("unexpected func formatter") 363 | } 364 | 365 | func formatCallpath(calldepth int, depth int) string { 366 | v := "" 367 | callers := make([]uintptr, 64) 368 | n := runtime.Callers(calldepth+2, callers) 369 | oldPc := callers[n-1] 370 | 371 | start := n - 3 372 | if depth > 0 && start >= depth { 373 | start = depth - 1 374 | v += "~." 375 | } 376 | recursiveCall := false 377 | for i := start; i >= 0; i-- { 378 | pc := callers[i] 379 | if oldPc == pc { 380 | recursiveCall = true 381 | continue 382 | } 383 | oldPc = pc 384 | if recursiveCall { 385 | recursiveCall = false 386 | v += ".." 387 | } 388 | if i < start { 389 | v += "." 390 | } 391 | if f := runtime.FuncForPC(pc); f != nil { 392 | v += formatFuncName(fmtVerbShortfunc, f.Name()) 393 | } 394 | } 395 | return v 396 | } 397 | 398 | // backendFormatter combines a backend with a specific formatter making it 399 | // possible to have different log formats for different backends. 400 | type backendFormatter struct { 401 | b Backend 402 | f Formatter 403 | } 404 | 405 | // NewBackendFormatter creates a new backend which makes all records that 406 | // passes through it being formatted by the specific formatter. 407 | func NewBackendFormatter(b Backend, f Formatter) Backend { 408 | return &backendFormatter{b, f} 409 | } 410 | 411 | // Log implements the Log function required by the Backend interface. 412 | func (bf *backendFormatter) Log(calldepth int, r *Record) { 413 | // Make a shallow copy of the record and replace any formatter 414 | r2 := *r 415 | r2.formatter = bf.f 416 | bf.b.Log(calldepth+1, &r2) 417 | } 418 | 419 | // Close closes the log service. 420 | func (bf *backendFormatter) Close() { 421 | bf.b.Close() 422 | } 423 | -------------------------------------------------------------------------------- /format_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Örjan Persson. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package logging 6 | 7 | import ( 8 | "bytes" 9 | "testing" 10 | ) 11 | 12 | func TestFormat(t *testing.T) { 13 | backend := InitForTesting(DEBUG) 14 | 15 | f, err := NewStringFormatter("%{shortfile} %{time:2006-01-02T15:04:05} %{level:.1s} %{id:04d} %{module} %{message}") 16 | if err != nil { 17 | t.Fatalf("failed to set format: %s", err) 18 | } 19 | SetFormatter(f) 20 | 21 | log := NewLogger("module") 22 | log.Debug("hello") 23 | 24 | line := MemoryRecordN(backend, 0).Formatted(0, false) 25 | if "format_test.go:24 1970-01-01T00:00:00 D 0001 module hello" != line { 26 | t.Errorf("Unexpected format: %s", line) 27 | } 28 | } 29 | 30 | func logAndGetLine(backend *MemoryBackend) string { 31 | NewLogger("foo").Debug("hello") 32 | return MemoryRecordN(backend, 0).Formatted(1, false) 33 | } 34 | 35 | func getLastLine(backend *MemoryBackend) string { 36 | return MemoryRecordN(backend, 0).Formatted(1, false) 37 | } 38 | 39 | func realFunc(backend *MemoryBackend) string { 40 | return logAndGetLine(backend) 41 | } 42 | 43 | type structFunc struct{} 44 | 45 | func (structFunc) Log(backend *MemoryBackend) string { 46 | return logAndGetLine(backend) 47 | } 48 | 49 | func TestRealFuncFormat(t *testing.T) { 50 | backend := InitForTesting(DEBUG) 51 | SetFormatter(MustStringFormatter("%{shortfunc}")) 52 | 53 | line := realFunc(backend) 54 | if "realFunc" != line { 55 | t.Errorf("Unexpected format: %s", line) 56 | } 57 | } 58 | 59 | func TestStructFuncFormat(t *testing.T) { 60 | backend := InitForTesting(DEBUG) 61 | SetFormatter(MustStringFormatter("%{longfunc}")) 62 | 63 | var x structFunc 64 | line := x.Log(backend) 65 | if "structFunc.Log" != line { 66 | t.Errorf("Unexpected format: %s", line) 67 | } 68 | } 69 | 70 | func TestVarFuncFormat(t *testing.T) { 71 | backend := InitForTesting(DEBUG) 72 | SetFormatter(MustStringFormatter("%{shortfunc}")) 73 | 74 | var varFunc = func() string { 75 | return logAndGetLine(backend) 76 | } 77 | 78 | line := varFunc() 79 | if "???" == line || "TestVarFuncFormat" == line || "varFunc" == line { 80 | t.Errorf("Unexpected format: %s", line) 81 | } 82 | } 83 | 84 | func TestFormatFuncName(t *testing.T) { 85 | var tests = []struct { 86 | filename string 87 | longpkg string 88 | shortpkg string 89 | longfunc string 90 | shortfunc string 91 | }{ 92 | {"", 93 | "???", 94 | "???", 95 | "???", 96 | "???"}, 97 | {"main", 98 | "???", 99 | "???", 100 | "???", 101 | "???"}, 102 | {"main.", 103 | "main", 104 | "main", 105 | "", 106 | ""}, 107 | {"main.main", 108 | "main", 109 | "main", 110 | "main", 111 | "main"}, 112 | {"github.com/op/go-logging.func·001", 113 | "github.com/op/go-logging", 114 | "go-logging", 115 | "func·001", 116 | "func·001"}, 117 | {"github.com/op/go-logging.stringFormatter.Format", 118 | "github.com/op/go-logging", 119 | "go-logging", 120 | "stringFormatter.Format", 121 | "Format"}, 122 | } 123 | 124 | var v string 125 | for _, test := range tests { 126 | v = formatFuncName(fmtVerbLongpkg, test.filename) 127 | if test.longpkg != v { 128 | t.Errorf("%s != %s", test.longpkg, v) 129 | } 130 | v = formatFuncName(fmtVerbShortpkg, test.filename) 131 | if test.shortpkg != v { 132 | t.Errorf("%s != %s", test.shortpkg, v) 133 | } 134 | v = formatFuncName(fmtVerbLongfunc, test.filename) 135 | if test.longfunc != v { 136 | t.Errorf("%s != %s", test.longfunc, v) 137 | } 138 | v = formatFuncName(fmtVerbShortfunc, test.filename) 139 | if test.shortfunc != v { 140 | t.Errorf("%s != %s", test.shortfunc, v) 141 | } 142 | } 143 | } 144 | 145 | func TestBackendFormatter(t *testing.T) { 146 | InitForTesting(DEBUG) 147 | 148 | // Create two backends and wrap one of the with a backend formatter 149 | b1 := NewMemoryBackend(1) 150 | b2 := NewMemoryBackend(1) 151 | 152 | f := MustStringFormatter("%{level} %{message}") 153 | bf := NewBackendFormatter(b2, f) 154 | 155 | SetBackend(b1, bf) 156 | 157 | log := NewLogger("module") 158 | log.Info("foo") 159 | if "foo" != getLastLine(b1) { 160 | t.Errorf("Unexpected line: %s", getLastLine(b1)) 161 | } 162 | if "INFO foo" != getLastLine(b2) { 163 | t.Errorf("Unexpected line: %s", getLastLine(b2)) 164 | } 165 | } 166 | 167 | func BenchmarkStringFormatter(b *testing.B) { 168 | fmt := "%{time:2006-01-02T15:04:05} %{level:.1s} %{id:04d} %{module} %{message}" 169 | f := MustStringFormatter(fmt) 170 | 171 | backend := InitForTesting(DEBUG) 172 | buf := &bytes.Buffer{} 173 | log := NewLogger("module") 174 | log.Debug("") 175 | record := MemoryRecordN(backend, 0) 176 | 177 | b.ResetTimer() 178 | for i := 0; i < b.N; i++ { 179 | if err := f.Format(1, false, record, buf); err != nil { 180 | b.Fatal(err) 181 | buf.Truncate(0) 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /level.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Örjan Persson. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package logging 6 | 7 | import ( 8 | "errors" 9 | "strings" 10 | "sync" 11 | ) 12 | 13 | // ErrInvalidLogLevel is used when an invalid log level has been used. 14 | var ErrInvalidLogLevel = errors.New("logger: invalid log level") 15 | 16 | // Level defines all available log levels for log messages. 17 | type Level int 18 | 19 | // Log levels. 20 | const ( 21 | OFF Level = iota 22 | PRINT 23 | CRITICAL 24 | ERROR 25 | WARNING 26 | NOTICE 27 | INFO 28 | DEBUG 29 | TRACE 30 | ) 31 | 32 | var levelNames = []string{ 33 | "OFF", 34 | "PRINT", 35 | "CRITICAL", 36 | "ERROR", 37 | "WARNING", 38 | "NOTICE", 39 | "INFO", 40 | "DEBUG", 41 | "TRACE", 42 | } 43 | 44 | // String returns the string representation of a logging level. 45 | func (p Level) String() string { 46 | return levelNames[p] 47 | } 48 | 49 | // LogLevel returns the log level from a string representation. 50 | func LogLevel(level string) (Level, error) { 51 | for i, name := range levelNames { 52 | if strings.EqualFold(name, level) { 53 | return Level(i), nil 54 | } 55 | } 56 | return ERROR, ErrInvalidLogLevel 57 | } 58 | 59 | // Leveled interface is the interface required to be able to add leveled 60 | // logging. 61 | type Leveled interface { 62 | GetLevel(string) Level 63 | SetLevel(Level, string) 64 | IsEnabledFor(Level, string) bool 65 | } 66 | 67 | // LeveledBackend is a log backend with additional knobs for setting levels on 68 | // individual modules to different levels. 69 | type LeveledBackend interface { 70 | Backend 71 | Leveled 72 | } 73 | 74 | type moduleLeveled struct { 75 | levels map[string]Level 76 | backend Backend 77 | formatter Formatter 78 | once sync.Once 79 | } 80 | 81 | // AddModuleLevel wraps a log backend with knobs to have different log levels 82 | // for different modules. 83 | func AddModuleLevel(backend Backend) LeveledBackend { 84 | var leveled LeveledBackend 85 | var ok bool 86 | if leveled, ok = backend.(LeveledBackend); !ok { 87 | leveled = &moduleLeveled{ 88 | levels: make(map[string]Level), 89 | backend: backend, 90 | } 91 | } 92 | return leveled 93 | } 94 | 95 | // DefaultLevel default level 96 | const DefaultLevel = TRACE 97 | 98 | // GetLevel returns the log level for the given module. 99 | func (l *moduleLeveled) GetLevel(module string) Level { 100 | level, exists := l.levels[module] 101 | if exists == false { 102 | level, exists = l.levels[""] 103 | // no configuration exists, sets default level 104 | if exists == false { 105 | level = DefaultLevel 106 | } 107 | } 108 | return level 109 | } 110 | 111 | // SetLevel sets the log level for the given module. 112 | func (l *moduleLeveled) SetLevel(level Level, module string) { 113 | l.levels[module] = level 114 | } 115 | 116 | // IsEnabledFor will return true if logging is enabled for the given module. 117 | func (l *moduleLeveled) IsEnabledFor(level Level, module string) bool { 118 | return level <= l.GetLevel(module) 119 | } 120 | 121 | func (l *moduleLeveled) Log(calldepth int, rec *Record) { 122 | if l.IsEnabledFor(rec.Level, rec.Module) { 123 | // TODO get rid of traces of formatter here. BackendFormatter should be used. 124 | rec.formatter = l.getFormatterAndCacheCurrent() 125 | l.backend.Log(calldepth+1, rec) 126 | } 127 | } 128 | 129 | // Close closes the log service. 130 | func (l *moduleLeveled) Close() { 131 | l.backend.Close() 132 | } 133 | 134 | func (l *moduleLeveled) getFormatterAndCacheCurrent() Formatter { 135 | l.once.Do(func() { 136 | if l.formatter == nil { 137 | l.formatter = getFormatter() 138 | } 139 | }) 140 | return l.formatter 141 | } 142 | -------------------------------------------------------------------------------- /level_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Örjan Persson. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package logging 6 | 7 | import "testing" 8 | 9 | func TestLevelString(t *testing.T) { 10 | // Make sure all levels can be converted from string -> constant -> string 11 | for _, name := range levelNames { 12 | level, err := LogLevel(name) 13 | if err != nil { 14 | t.Errorf("failed to get level: %v", err) 15 | continue 16 | } 17 | 18 | if level.String() != name { 19 | t.Errorf("invalid level conversion: %v != %v", level, name) 20 | } 21 | } 22 | } 23 | 24 | func TestLevelLogLevel(t *testing.T) { 25 | tests := []struct { 26 | expected Level 27 | level string 28 | }{ 29 | {-1, "bla"}, 30 | {INFO, "iNfO"}, 31 | {ERROR, "error"}, 32 | {WARNING, "warninG"}, 33 | } 34 | 35 | for _, test := range tests { 36 | level, err := LogLevel(test.level) 37 | if err != nil { 38 | if test.expected == -1 { 39 | continue 40 | } else { 41 | t.Errorf("failed to convert %s: %s", test.level, err) 42 | } 43 | } 44 | if test.expected != level { 45 | t.Errorf("failed to convert %s to level: %s != %s", test.level, test.expected, level) 46 | } 47 | } 48 | } 49 | 50 | func TestLevelModuleLevel(t *testing.T) { 51 | backend := NewMemoryBackend(128) 52 | 53 | leveled := AddModuleLevel(backend) 54 | leveled.SetLevel(NOTICE, "") 55 | leveled.SetLevel(ERROR, "foo") 56 | leveled.SetLevel(INFO, "foo.bar") 57 | leveled.SetLevel(WARNING, "bar") 58 | 59 | expected := []struct { 60 | level Level 61 | module string 62 | }{ 63 | {NOTICE, ""}, 64 | {NOTICE, "something"}, 65 | {ERROR, "foo"}, 66 | {INFO, "foo.bar"}, 67 | {WARNING, "bar"}, 68 | } 69 | 70 | for _, e := range expected { 71 | actual := leveled.GetLevel(e.module) 72 | if e.level != actual { 73 | t.Errorf("unexpected level in %s: %s != %s", e.module, e.level, actual) 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /logger.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Örjan Persson. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package logging implements a logging infrastructure for Go. It supports 6 | // different logging backends like syslog, file and memory. Multiple backends 7 | // can be utilized with different log levels per backend and logger. 8 | package logging 9 | 10 | import ( 11 | "bytes" 12 | "fmt" 13 | "log" 14 | "os" 15 | "strings" 16 | "sync" 17 | "sync/atomic" 18 | "time" 19 | ) 20 | 21 | // Redactor is an interface for types that may contain sensitive information 22 | // (like passwords), which shouldn't be printed to the log. The idea was found 23 | // in relog as part of the vitness project. 24 | type Redactor interface { 25 | Redacted() interface{} 26 | } 27 | 28 | // Redact returns a string of * having the same length as s. 29 | func Redact(s string) string { 30 | return strings.Repeat("*", len(s)) 31 | } 32 | 33 | var ( 34 | // Sequence number is incremented and utilized for all log records created. 35 | sequenceNo uint64 36 | 37 | // timeNow is a customizable for testing purposes. 38 | timeNow = time.Now 39 | ) 40 | 41 | // Record represents a log record and contains the timestamp when the record 42 | // was created, an increasing id, filename and line and finally the actual 43 | // formatted log line. 44 | type Record struct { 45 | ID uint64 46 | Time time.Time 47 | Module string 48 | Level Level 49 | Args []interface{} 50 | 51 | // message is kept as a pointer to have shallow copies update this once 52 | // needed. 53 | message *string 54 | fmt *string 55 | formatter Formatter 56 | formatted string 57 | } 58 | 59 | var recordPool = &sync.Pool{ 60 | New: func() interface{} { 61 | return &Record{} 62 | }, 63 | } 64 | 65 | // Formatted returns the formatted log record string. 66 | func (r *Record) Formatted(calldepth int, colorful bool) string { 67 | if r.formatted == "" { 68 | var buf bytes.Buffer 69 | r.formatter.Format(calldepth+1, colorful, r, &buf) 70 | r.formatted = buf.String() 71 | } 72 | return r.formatted 73 | } 74 | 75 | // Message returns the log record message. 76 | func (r *Record) Message() string { 77 | if r.message == nil { 78 | // Redact the arguments that implements the Redactor interface 79 | for i, arg := range r.Args { 80 | if redactor, ok := arg.(Redactor); ok == true { 81 | r.Args[i] = redactor.Redacted() 82 | } 83 | } 84 | var buf bytes.Buffer 85 | if r.fmt != nil { 86 | fmt.Fprintf(&buf, *r.fmt, r.Args...) 87 | } else { 88 | // use Fprintln to make sure we always get space between arguments 89 | fmt.Fprintln(&buf, r.Args...) 90 | buf.Truncate(buf.Len() - 1) // strip newline 91 | } 92 | msg := buf.String() 93 | r.message = &msg 94 | } 95 | return *r.message 96 | } 97 | 98 | // Logger is the actual logger which creates log records based on the functions 99 | // called and passes them to the underlying logging backend. 100 | type Logger struct { 101 | Module string 102 | backend LeveledBackend 103 | haveBackend bool 104 | 105 | // ExtraCallDepth can be used to add additional call depth when getting the 106 | // calling function. This is normally used when wrapping a logger. 107 | ExtraCalldepth int 108 | 109 | status int8 // 0:close 1:run 110 | lock sync.RWMutex 111 | } 112 | 113 | // NewLogger creates and returns a Logger object based on the module name. 114 | func NewLogger(module string) *Logger { 115 | return &Logger{ 116 | Module: module, 117 | status: 1, 118 | } 119 | } 120 | 121 | // Reset restores the internal state of the logging library. 122 | func Reset() { 123 | // TODO make a global Init() method to be less magic? or make it such that 124 | // if there's no backends at all configured, we could use some tricks to 125 | // automatically setup backends based if we have a TTY or not. 126 | sequenceNo = 0 127 | b := SetBackend(NewLogBackend(os.Stderr, "", log.LstdFlags)) 128 | b.SetLevel(DefaultLevel, "") 129 | SetFormatter(DefaultFormatter) 130 | timeNow = time.Now 131 | } 132 | 133 | // SetBackend overrides any previously defined backend for this logger. 134 | func (l *Logger) SetBackend(backend LeveledBackend) { 135 | l.backend = backend 136 | l.haveBackend = true 137 | } 138 | 139 | // IsEnabledFor returns true if the logger is enabled for the given level. 140 | func (l *Logger) IsEnabledFor(level Level) bool { 141 | return defaultBackend.IsEnabledFor(level, l.Module) 142 | } 143 | 144 | // Close waits until all records in the buffered channel have been processed and close service. 145 | func (l *Logger) Close() { 146 | l.lock.Lock() 147 | l.status = 0 148 | l.lock.Unlock() 149 | if l.haveBackend { 150 | l.backend.Close() 151 | } 152 | defaultBackend.Close() 153 | } 154 | 155 | func (l *Logger) log(lvl Level, format *string, args ...interface{}) { 156 | l.lock.RLock() 157 | if l.status == 0 { 158 | l.lock.RUnlock() 159 | return 160 | } 161 | if !l.IsEnabledFor(lvl) { 162 | l.lock.RUnlock() 163 | return 164 | } 165 | // Get the logging record and pass it in to the backend 166 | record := recordPool.Get().(*Record) 167 | { 168 | record.ID = atomic.AddUint64(&sequenceNo, 1) 169 | record.Time = timeNow() 170 | record.Module = l.Module 171 | record.Level = lvl 172 | record.Args = args 173 | record.fmt = format 174 | 175 | record.formatter = nil 176 | record.message = nil 177 | record.formatted = "" 178 | } 179 | 180 | // TODO use channels to fan out the records to all backends? 181 | // TODO in case of errors, do something (tricky) 182 | 183 | // calldepth=2 brings the stack up to the caller of the level 184 | // methods, Info(), Fatal(), etc. 185 | // ExtraCallDepth allows this to be extended further up the stack in case we 186 | // are wrapping these methods, eg. to expose them package level 187 | if l.haveBackend { 188 | l.backend.Log(2+l.ExtraCalldepth, record) 189 | } else { 190 | defaultBackend.Log(2+l.ExtraCalldepth, record) 191 | } 192 | l.lock.RUnlock() 193 | recordPool.Put(record) 194 | } 195 | 196 | // Fatal is equivalent to l.Critical(fmt.Sprint()) followed by a call to os.Exit(1). 197 | func (l *Logger) Fatal(args ...interface{}) { 198 | l.log(CRITICAL, nil, args...) 199 | os.Exit(1) 200 | } 201 | 202 | // Fatalf is equivalent to l.Critical followed by a call to os.Exit(1). 203 | func (l *Logger) Fatalf(format string, args ...interface{}) { 204 | l.log(CRITICAL, &format, args...) 205 | os.Exit(1) 206 | } 207 | 208 | // Panic is equivalent to l.Critical(fmt.Sprint()) followed by a call to panic(). 209 | func (l *Logger) Panic(args ...interface{}) { 210 | l.log(CRITICAL, nil, args...) 211 | panic(fmt.Sprint(args...)) 212 | } 213 | 214 | // Panicf is equivalent to l.Critical followed by a call to panic(). 215 | func (l *Logger) Panicf(format string, args ...interface{}) { 216 | l.log(CRITICAL, &format, args...) 217 | panic(fmt.Sprintf(format, args...)) 218 | } 219 | 220 | // Critical logs a message using CRITICAL as log level. 221 | func (l *Logger) Critical(args ...interface{}) { 222 | l.log(CRITICAL, nil, args...) 223 | } 224 | 225 | // Criticalf logs a message using CRITICAL as log level. 226 | func (l *Logger) Criticalf(format string, args ...interface{}) { 227 | l.log(CRITICAL, &format, args...) 228 | } 229 | 230 | // Error logs a message using ERROR as log level. 231 | func (l *Logger) Error(args ...interface{}) { 232 | l.log(ERROR, nil, args...) 233 | } 234 | 235 | // Errorf logs a message using ERROR as log level. 236 | func (l *Logger) Errorf(format string, args ...interface{}) { 237 | l.log(ERROR, &format, args...) 238 | } 239 | 240 | // Warning logs a message using WARNING as log level. 241 | func (l *Logger) Warning(args ...interface{}) { 242 | l.log(WARNING, nil, args...) 243 | } 244 | 245 | // Warningf logs a message using WARNING as log level. 246 | func (l *Logger) Warningf(format string, args ...interface{}) { 247 | l.log(WARNING, &format, args...) 248 | } 249 | 250 | // Warn is an alias for Warning. 251 | func (l *Logger) Warn(args ...interface{}) { 252 | l.log(WARNING, nil, args...) 253 | } 254 | 255 | // Warnf is an alias for Warningf. 256 | func (l *Logger) Warnf(format string, args ...interface{}) { 257 | l.log(WARNING, &format, args...) 258 | } 259 | 260 | // Notice logs a message using NOTICE as log level. 261 | func (l *Logger) Notice(args ...interface{}) { 262 | l.log(NOTICE, nil, args...) 263 | } 264 | 265 | // Noticef logs a message using NOTICE as log level. 266 | func (l *Logger) Noticef(format string, args ...interface{}) { 267 | l.log(NOTICE, &format, args...) 268 | } 269 | 270 | // Info logs a message using INFO as log level. 271 | func (l *Logger) Info(args ...interface{}) { 272 | l.log(INFO, nil, args...) 273 | } 274 | 275 | // Infof logs a message using INFO as log level. 276 | func (l *Logger) Infof(format string, args ...interface{}) { 277 | l.log(INFO, &format, args...) 278 | } 279 | 280 | // Debug logs a message using DEBUG as log level. 281 | func (l *Logger) Debug(args ...interface{}) { 282 | l.log(DEBUG, nil, args...) 283 | } 284 | 285 | // Debugf logs a message using DEBUG as log level. 286 | func (l *Logger) Debugf(format string, args ...interface{}) { 287 | l.log(DEBUG, &format, args...) 288 | } 289 | 290 | // Trace logs a message using TRACE as log level. 291 | func (l *Logger) Trace(args ...interface{}) { 292 | l.log(TRACE, nil, args...) 293 | } 294 | 295 | // Tracef logs a message using TRACE as log level. 296 | func (l *Logger) Tracef(format string, args ...interface{}) { 297 | l.log(TRACE, &format, args...) 298 | } 299 | 300 | // Print formats using the default formats for its operands and writes to standard output. 301 | // Spaces are added between operands when neither is a string. 302 | // It returns the number of bytes written and any write error encountered. 303 | func (l *Logger) Print(args ...interface{}) { 304 | l.log(PRINT, nil, args...) 305 | } 306 | 307 | // Printf formats according to a format specifier and writes to standard output. 308 | // It returns the number of bytes written and any write error encountered. 309 | func (l *Logger) Printf(format string, args ...interface{}) { 310 | l.log(PRINT, &format, args...) 311 | } 312 | 313 | func init() { 314 | Reset() 315 | } 316 | -------------------------------------------------------------------------------- /logger_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Örjan Persson. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package logging 6 | 7 | import "testing" 8 | 9 | type Password string 10 | 11 | func (p Password) Redacted() interface{} { 12 | return Redact(string(p)) 13 | } 14 | 15 | func TestSequenceNoOverflow(t *testing.T) { 16 | // Forcefully set the next sequence number to the maximum 17 | backend := InitForTesting(DEBUG) 18 | sequenceNo = ^uint64(0) 19 | 20 | log := NewLogger("test") 21 | log.Debug("test") 22 | 23 | if MemoryRecordN(backend, 0).ID != 0 { 24 | t.Errorf("Unexpected sequence no: %v", MemoryRecordN(backend, 0).ID) 25 | } 26 | } 27 | 28 | func TestRedact(t *testing.T) { 29 | backend := InitForTesting(DEBUG) 30 | password := Password("123456") 31 | log := NewLogger("test") 32 | log.Debug("foo", password) 33 | if "foo ******" != MemoryRecordN(backend, 0).Formatted(0, false) { 34 | t.Errorf("redacted line: %v", MemoryRecordN(backend, 0)) 35 | } 36 | } 37 | 38 | func TestRedactf(t *testing.T) { 39 | backend := InitForTesting(DEBUG) 40 | password := Password("123456") 41 | log := NewLogger("test") 42 | log.Debugf("foo %s", password) 43 | if "foo ******" != MemoryRecordN(backend, 0).Formatted(0, false) { 44 | t.Errorf("redacted line: %v", MemoryRecordN(backend, 0).Formatted(0, false)) 45 | } 46 | } 47 | 48 | func TestPrivateBackend(t *testing.T) { 49 | stdBackend := InitForTesting(DEBUG) 50 | log := NewLogger("test") 51 | privateBackend := NewMemoryBackend(10240) 52 | lvlBackend := AddModuleLevel(privateBackend) 53 | lvlBackend.SetLevel(DEBUG, "") 54 | log.SetBackend(lvlBackend) 55 | log.Debug("to private backend") 56 | if stdBackend.size > 0 { 57 | t.Errorf("something in stdBackend, size of backend: %d", stdBackend.size) 58 | } 59 | if "to private baсkend" == MemoryRecordN(privateBackend, 0).Formatted(0, false) { 60 | t.Error("logged to defaultBackend:", MemoryRecordN(privateBackend, 0)) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /memory.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Örjan Persson. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build !appengine 6 | 7 | package logging 8 | 9 | import ( 10 | "sync" 11 | "sync/atomic" 12 | "time" 13 | "unsafe" 14 | ) 15 | 16 | // TODO pick one of the memory backends and stick with it or share interface. 17 | 18 | // InitForTesting is a convenient method when using logging in a test. Once 19 | // called, the time will be frozen to January 1, 1970 UTC. 20 | func InitForTesting(level Level) *MemoryBackend { 21 | Reset() 22 | 23 | memoryBackend := NewMemoryBackend(10240) 24 | 25 | leveledBackend := AddModuleLevel(memoryBackend) 26 | leveledBackend.SetLevel(level, "") 27 | SetBackend(leveledBackend) 28 | 29 | timeNow = func() time.Time { 30 | return time.Unix(0, 0).UTC() 31 | } 32 | return memoryBackend 33 | } 34 | 35 | // Node is a record node pointing to an optional next node. 36 | type node struct { 37 | next *node 38 | Record *Record 39 | } 40 | 41 | // Next returns the next record node. If there's no node available, it will 42 | // return nil. 43 | func (n *node) Next() *node { 44 | return n.next 45 | } 46 | 47 | // MemoryBackend is a simple memory based logging backend that will not produce 48 | // any output but merly keep records, up to the given size, in memory. 49 | type MemoryBackend struct { 50 | size int32 51 | maxSize int32 52 | head, tail unsafe.Pointer 53 | } 54 | 55 | // NewMemoryBackend creates a simple in-memory logging backend. 56 | func NewMemoryBackend(size int) *MemoryBackend { 57 | return &MemoryBackend{maxSize: int32(size)} 58 | } 59 | 60 | // Log implements the Log method required by Backend. 61 | func (b *MemoryBackend) Log(calldepth int, rec *Record) { 62 | var size int32 63 | 64 | n := &node{Record: rec} 65 | np := unsafe.Pointer(n) 66 | 67 | // Add the record to the tail. If there's no records available, tail and 68 | // head will both be nil. When we successfully set the tail and the previous 69 | // value was nil, it's safe to set the head to the current value too. 70 | for { 71 | tailp := b.tail 72 | swapped := atomic.CompareAndSwapPointer( 73 | &b.tail, 74 | tailp, 75 | np, 76 | ) 77 | if swapped == true { 78 | if tailp == nil { 79 | b.head = np 80 | } else { 81 | (*node)(tailp).next = n 82 | } 83 | size = atomic.AddInt32(&b.size, 1) 84 | break 85 | } 86 | } 87 | 88 | // Since one record was added, we might have overflowed the list. Remove 89 | // a record if that is the case. The size will fluctate a bit, but 90 | // eventual consistent. 91 | if b.maxSize > 0 && size > b.maxSize { 92 | for { 93 | headp := b.head 94 | head := (*node)(b.head) 95 | if head.next == nil { 96 | break 97 | } 98 | swapped := atomic.CompareAndSwapPointer( 99 | &b.head, 100 | headp, 101 | unsafe.Pointer(head.next), 102 | ) 103 | if swapped == true { 104 | atomic.AddInt32(&b.size, -1) 105 | break 106 | } 107 | } 108 | } 109 | } 110 | 111 | // Close closes the log service. 112 | func (b *MemoryBackend) Close() {} 113 | 114 | // Head returns the oldest record node kept in memory. It can be used to 115 | // iterate over records, one by one, up to the last record. 116 | // 117 | // Note: new records can get added while iterating. Hence the number of records 118 | // iterated over might be larger than the maximum size. 119 | func (b *MemoryBackend) Head() *node { 120 | return (*node)(b.head) 121 | } 122 | 123 | type event int 124 | 125 | const ( 126 | eventFlush event = iota 127 | eventStop 128 | ) 129 | 130 | // ChannelMemoryBackend is very similar to the MemoryBackend, except that it 131 | // internally utilizes a channel. 132 | type ChannelMemoryBackend struct { 133 | maxSize int 134 | size int 135 | incoming chan *Record 136 | events chan event 137 | mu sync.Mutex 138 | running bool 139 | flushWg sync.WaitGroup 140 | stopWg sync.WaitGroup 141 | head, tail *node 142 | } 143 | 144 | // NewChannelMemoryBackend creates a simple in-memory logging backend which 145 | // utilizes a go channel for communication. 146 | // 147 | // Start will automatically be called by this function. 148 | func NewChannelMemoryBackend(size int) *ChannelMemoryBackend { 149 | backend := &ChannelMemoryBackend{ 150 | maxSize: size, 151 | incoming: make(chan *Record, 1024), 152 | events: make(chan event), 153 | } 154 | backend.Start() 155 | return backend 156 | } 157 | 158 | // Start launches the internal goroutine which starts processing data from the 159 | // input channel. 160 | func (b *ChannelMemoryBackend) Start() { 161 | b.mu.Lock() 162 | defer b.mu.Unlock() 163 | 164 | // Launch the goroutine unless it's already running. 165 | if b.running != true { 166 | b.running = true 167 | b.stopWg.Add(1) 168 | go b.process() 169 | } 170 | } 171 | 172 | func (b *ChannelMemoryBackend) process() { 173 | defer b.stopWg.Done() 174 | for { 175 | select { 176 | case rec := <-b.incoming: 177 | b.insertRecord(rec) 178 | case e := <-b.events: 179 | switch e { 180 | case eventStop: 181 | return 182 | case eventFlush: 183 | for len(b.incoming) > 0 { 184 | b.insertRecord(<-b.incoming) 185 | } 186 | b.flushWg.Done() 187 | } 188 | } 189 | } 190 | } 191 | 192 | func (b *ChannelMemoryBackend) insertRecord(rec *Record) { 193 | prev := b.tail 194 | b.tail = &node{Record: rec} 195 | if prev == nil { 196 | b.head = b.tail 197 | } else { 198 | prev.next = b.tail 199 | } 200 | 201 | if b.maxSize > 0 && b.size >= b.maxSize { 202 | b.head = b.head.next 203 | } else { 204 | b.size++ 205 | } 206 | } 207 | 208 | // Flush waits until all records in the buffered channel have been processed. 209 | func (b *ChannelMemoryBackend) Flush() { 210 | b.flushWg.Add(1) 211 | b.events <- eventFlush 212 | b.flushWg.Wait() 213 | } 214 | 215 | // Close signals the internal goroutine to exit and waits until it have. 216 | func (b *ChannelMemoryBackend) Close() { 217 | b.mu.Lock() 218 | if b.running == true { 219 | b.running = false 220 | b.events <- eventStop 221 | } 222 | b.mu.Unlock() 223 | b.stopWg.Wait() 224 | } 225 | 226 | // Log implements the Log method required by Backend. 227 | func (b *ChannelMemoryBackend) Log(calldepth int, rec *Record) { 228 | b.incoming <- rec 229 | } 230 | 231 | // Head returns the oldest record node kept in memory. It can be used to 232 | // iterate over records, one by one, up to the last record. 233 | // 234 | // Note: new records can get added while iterating. Hence the number of records 235 | // iterated over might be larger than the maximum size. 236 | func (b *ChannelMemoryBackend) Head() *node { 237 | return b.head 238 | } 239 | -------------------------------------------------------------------------------- /memory_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Örjan Persson. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package logging 6 | 7 | // TODO share more code between these tests 8 | func MemoryRecordN(b *MemoryBackend, n int) *Record { 9 | node := b.Head() 10 | for i := 0; i < n; i++ { 11 | if node == nil { 12 | break 13 | } 14 | node = node.Next() 15 | } 16 | if node == nil { 17 | return nil 18 | } 19 | return node.Record 20 | } 21 | 22 | func ChannelMemoryRecordN(b *ChannelMemoryBackend, n int) *Record { 23 | b.Flush() 24 | node := b.Head() 25 | for i := 0; i < n; i++ { 26 | if node == nil { 27 | break 28 | } 29 | node = node.Next() 30 | } 31 | if node == nil { 32 | return nil 33 | } 34 | return node.Record 35 | } 36 | -------------------------------------------------------------------------------- /multi.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Örjan Persson. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package logging 6 | 7 | // TODO remove Level stuff from the multi logger. Do one thing. 8 | 9 | // multiLogger is a log multiplexer which can be used to utilize multiple log 10 | // backends at once. 11 | type multiLogger struct { 12 | backends []LeveledBackend 13 | } 14 | 15 | // MultiLogger creates a logger which contain multiple loggers. 16 | func MultiLogger(backends ...Backend) LeveledBackend { 17 | var leveledBackends []LeveledBackend 18 | for _, backend := range backends { 19 | leveledBackends = append(leveledBackends, AddModuleLevel(backend)) 20 | } 21 | return &multiLogger{leveledBackends} 22 | } 23 | 24 | // Log passes the log record to all backends. 25 | func (b *multiLogger) Log(calldepth int, rec *Record) { 26 | for _, backend := range b.backends { 27 | if backend.IsEnabledFor(rec.Level, rec.Module) { 28 | // Shallow copy of the record for the formatted cache on Record and get the 29 | // record formatter from the backend. 30 | r2 := *rec 31 | backend.Log(calldepth+1, &r2) 32 | } 33 | } 34 | } 35 | 36 | // Close closes the log service. 37 | func (b *multiLogger) Close() { 38 | for _, backend := range b.backends { 39 | backend.Close() 40 | } 41 | } 42 | 43 | // GetLevel returns the highest level enabled by all backends. 44 | func (b *multiLogger) GetLevel(module string) Level { 45 | var level Level 46 | for _, backend := range b.backends { 47 | if backendLevel := backend.GetLevel(module); backendLevel > level { 48 | level = backendLevel 49 | } 50 | } 51 | return level 52 | } 53 | 54 | // SetLevel propagates the same level to all backends. 55 | func (b *multiLogger) SetLevel(level Level, module string) { 56 | for _, backend := range b.backends { 57 | backend.SetLevel(level, module) 58 | } 59 | } 60 | 61 | // IsEnabledFor returns true if any of the backends are enabled for it. 62 | func (b *multiLogger) IsEnabledFor(level Level, module string) bool { 63 | for _, backend := range b.backends { 64 | if backend.IsEnabledFor(level, module) { 65 | return true 66 | } 67 | } 68 | return false 69 | } 70 | -------------------------------------------------------------------------------- /multi_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Örjan Persson. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package logging 6 | 7 | import "testing" 8 | 9 | func TestMultiLogger(t *testing.T) { 10 | log1 := NewMemoryBackend(8) 11 | log2 := NewMemoryBackend(8) 12 | SetBackend(MultiLogger(log1, log2)) 13 | 14 | log := NewLogger("test") 15 | log.Debug("log") 16 | 17 | if "log" != MemoryRecordN(log1, 0).Formatted(0, false) { 18 | t.Errorf("log1: %v", MemoryRecordN(log1, 0).Formatted(0, false)) 19 | } 20 | if "log" != MemoryRecordN(log2, 0).Formatted(0, false) { 21 | t.Errorf("log2: %v", MemoryRecordN(log2, 0).Formatted(0, false)) 22 | } 23 | } 24 | 25 | func TestMultiLoggerLevel(t *testing.T) { 26 | log1 := NewMemoryBackend(8) 27 | log2 := NewMemoryBackend(8) 28 | 29 | leveled1 := AddModuleLevel(log1) 30 | leveled2 := AddModuleLevel(log2) 31 | 32 | multi := MultiLogger(leveled1, leveled2) 33 | multi.SetLevel(ERROR, "test") 34 | SetBackend(multi) 35 | 36 | log := NewLogger("test") 37 | log.Notice("log") 38 | 39 | if nil != MemoryRecordN(log1, 0) || nil != MemoryRecordN(log2, 0) { 40 | t.Errorf("unexpected log record") 41 | } 42 | 43 | leveled1.SetLevel(DEBUG, "test") 44 | log.Notice("log") 45 | if "log" != MemoryRecordN(log1, 0).Formatted(0, false) { 46 | t.Errorf("log1 not received") 47 | } 48 | if nil != MemoryRecordN(log2, 0) { 49 | t.Errorf("log2 received") 50 | } 51 | } 52 | --------------------------------------------------------------------------------