1// Copyright (c) 2012 - Cloud Instruments Co., Ltd. 2// 3// All rights reserved. 4// 5// Redistribution and use in source and binary forms, with or without 6// modification, are permitted provided that the following conditions are met: 7// 8// 1. Redistributions of source code must retain the above copyright notice, this 9// list of conditions and the following disclaimer. 10// 2. Redistributions in binary form must reproduce the above copyright notice, 11// this list of conditions and the following disclaimer in the documentation 12// and/or other materials provided with the distribution. 13// 14// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 25package seelog 26 27import ( 28 "errors" 29 "fmt" 30 "os" 31 "path/filepath" 32 "runtime" 33 "strings" 34 "sync" 35 "time" 36) 37 38var ( 39 workingDir = "/" 40 stackCache map[uintptr]*logContext 41 stackCacheLock sync.RWMutex 42) 43 44func init() { 45 wd, err := os.Getwd() 46 if err == nil { 47 workingDir = filepath.ToSlash(wd) + "/" 48 } 49 stackCache = make(map[uintptr]*logContext) 50} 51 52// Represents runtime caller context. 53type LogContextInterface interface { 54 // Caller's function name. 55 Func() string 56 // Caller's line number. 57 Line() int 58 // Caller's file short path (in slashed form). 59 ShortPath() string 60 // Caller's file full path (in slashed form). 61 FullPath() string 62 // Caller's file name (without path). 63 FileName() string 64 // True if the context is correct and may be used. 65 // If false, then an error in context evaluation occurred and 66 // all its other data may be corrupted. 67 IsValid() bool 68 // Time when log function was called. 69 CallTime() time.Time 70 // Custom context that can be set by calling logger.SetContext 71 CustomContext() interface{} 72} 73 74// Returns context of the caller 75func currentContext(custom interface{}) (LogContextInterface, error) { 76 return specifyContext(1, custom) 77} 78 79func extractCallerInfo(skip int) (*logContext, error) { 80 var stack [1]uintptr 81 if runtime.Callers(skip+1, stack[:]) != 1 { 82 return nil, errors.New("error during runtime.Callers") 83 } 84 pc := stack[0] 85 86 // do we have a cache entry? 87 stackCacheLock.RLock() 88 ctx, ok := stackCache[pc] 89 stackCacheLock.RUnlock() 90 if ok { 91 return ctx, nil 92 } 93 94 // look up the details of the given caller 95 funcInfo := runtime.FuncForPC(pc) 96 if funcInfo == nil { 97 return nil, errors.New("error during runtime.FuncForPC") 98 } 99 100 var shortPath string 101 fullPath, line := funcInfo.FileLine(pc) 102 if strings.HasPrefix(fullPath, workingDir) { 103 shortPath = fullPath[len(workingDir):] 104 } else { 105 shortPath = fullPath 106 } 107 funcName := funcInfo.Name() 108 if strings.HasPrefix(funcName, workingDir) { 109 funcName = funcName[len(workingDir):] 110 } 111 112 ctx = &logContext{ 113 funcName: funcName, 114 line: line, 115 shortPath: shortPath, 116 fullPath: fullPath, 117 fileName: filepath.Base(fullPath), 118 } 119 120 // save the details in the cache; note that it's possible we might 121 // have written an entry into the map in between the test above and 122 // this section, but the behaviour is still correct 123 stackCacheLock.Lock() 124 stackCache[pc] = ctx 125 stackCacheLock.Unlock() 126 return ctx, nil 127} 128 129// Returns context of the function with placed "skip" stack frames of the caller 130// If skip == 0 then behaves like currentContext 131// Context is returned in any situation, even if error occurs. But, if an error 132// occurs, the returned context is an error context, which contains no paths 133// or names, but states that they can't be extracted. 134func specifyContext(skip int, custom interface{}) (LogContextInterface, error) { 135 callTime := time.Now() 136 if skip < 0 { 137 err := fmt.Errorf("can not skip negative stack frames") 138 return &errorContext{callTime, err}, err 139 } 140 caller, err := extractCallerInfo(skip + 2) 141 if err != nil { 142 return &errorContext{callTime, err}, err 143 } 144 ctx := new(logContext) 145 *ctx = *caller 146 ctx.callTime = callTime 147 ctx.custom = custom 148 return ctx, nil 149} 150 151// Represents a normal runtime caller context. 152type logContext struct { 153 funcName string 154 line int 155 shortPath string 156 fullPath string 157 fileName string 158 callTime time.Time 159 custom interface{} 160} 161 162func (context *logContext) IsValid() bool { 163 return true 164} 165 166func (context *logContext) Func() string { 167 return context.funcName 168} 169 170func (context *logContext) Line() int { 171 return context.line 172} 173 174func (context *logContext) ShortPath() string { 175 return context.shortPath 176} 177 178func (context *logContext) FullPath() string { 179 return context.fullPath 180} 181 182func (context *logContext) FileName() string { 183 return context.fileName 184} 185 186func (context *logContext) CallTime() time.Time { 187 return context.callTime 188} 189 190func (context *logContext) CustomContext() interface{} { 191 return context.custom 192} 193 194// Represents an error context 195type errorContext struct { 196 errorTime time.Time 197 err error 198} 199 200func (errContext *errorContext) getErrorText(prefix string) string { 201 return fmt.Sprintf("%s() error: %s", prefix, errContext.err) 202} 203 204func (errContext *errorContext) IsValid() bool { 205 return false 206} 207 208func (errContext *errorContext) Line() int { 209 return -1 210} 211 212func (errContext *errorContext) Func() string { 213 return errContext.getErrorText("Func") 214} 215 216func (errContext *errorContext) ShortPath() string { 217 return errContext.getErrorText("ShortPath") 218} 219 220func (errContext *errorContext) FullPath() string { 221 return errContext.getErrorText("FullPath") 222} 223 224func (errContext *errorContext) FileName() string { 225 return errContext.getErrorText("FileName") 226} 227 228func (errContext *errorContext) CallTime() time.Time { 229 return errContext.errorTime 230} 231 232func (errContext *errorContext) CustomContext() interface{} { 233 return nil 234} 235