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