/* * Iodine, (C) 2015 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package iodine import ( "bytes" "encoding/json" "errors" "fmt" "os" "path" "reflect" "runtime" "strconv" "strings" "sync" "github.com/dustin/go-humanize" ) // Error is the iodine error which contains a pointer to the original error // and stack traces. type Error struct { EmbeddedError error `json:"-"` ErrorMessage string ErrorType string Stack []StackEntry } // StackEntry contains the entry in the stack trace type StackEntry struct { Host string File string Func string Line int Data map[string]string } var gopath string var globalState = struct { sync.RWMutex m map[string]string }{m: make(map[string]string)} // SetGlobalState - set global state func SetGlobalState(key, value string) { globalState.Lock() globalState.m[key] = value globalState.Unlock() } // ClearGlobalState - clear info in globalState struct func ClearGlobalState() { globalState.Lock() for k := range globalState.m { delete(globalState.m, k) } globalState.Unlock() } // GetGlobalState - get map from globalState struct func GetGlobalState() map[string]string { result := make(map[string]string) globalState.RLock() for k, v := range globalState.m { result[k] = v } globalState.RUnlock() return result } // GetGlobalStateKey - get value for key from globalState struct func GetGlobalStateKey(k string) string { result, ok := globalState.m[k] if !ok { return "" } return result } // ToError returns the input if it is not an iodine error. It returns the embedded error if it is an iodine error. If nil, returns nil. func ToError(err error) error { switch err := err.(type) { case nil: { return nil } case Error: { if err.EmbeddedError != nil { return err.EmbeddedError } return errors.New(err.ErrorMessage) } default: { return err } } } // New - instantiate an error, turning it into an iodine error. // Adds an initial stack trace. func New(err error, data map[string]string) error { if err != nil { entry := createStackEntry() var newErr Error // check if error is wrapped switch typedError := err.(type) { case Error: { newErr = typedError } default: { newErr = Error{ EmbeddedError: err, ErrorMessage: err.Error(), ErrorType: reflect.TypeOf(err).String(), Stack: []StackEntry{}, } } } for k, v := range data { entry.Data[k] = v } newErr.Stack = append(newErr.Stack, entry) return newErr } return nil } // createStackEntry - create stack entries func createStackEntry() StackEntry { host, _ := os.Hostname() pc, file, line, _ := runtime.Caller(2) function := runtime.FuncForPC(pc).Name() _, function = path.Split(function) file = strings.TrimPrefix(file, gopath) // trim gopath from file data := GetGlobalState() for k, v := range getSystemData() { data[k] = v } entry := StackEntry{ Host: host, File: file, Func: function, Line: line, Data: data, } return entry } func getSystemData() map[string]string { host, err := os.Hostname() if err != nil { host = "" } memstats := &runtime.MemStats{} runtime.ReadMemStats(memstats) return map[string]string{ "sys.host": host, "sys.os": runtime.GOOS, "sys.arch": runtime.GOARCH, "sys.go": runtime.Version(), "sys.cpus": strconv.Itoa(runtime.NumCPU()), "sys.mem.used": humanize.Bytes(memstats.Alloc), "sys.mem.allocated": humanize.Bytes(memstats.TotalAlloc), "sys.mem.heap.used": humanize.Bytes(memstats.HeapAlloc), "sys.mem.heap.allocated": humanize.Bytes(memstats.HeapSys), } } // Annotate an error with a stack entry and returns itself //func (err *WrappedError) Annotate(info map[string]string) *WrappedError { // entry := createStackEntry() // for k, v := range info { // entry.Data[k] = v // } // err.Stack = append(err.Stack, entry) // return err //} // EmitJSON writes JSON output for the error func (err Error) EmitJSON() ([]byte, error) { return json.Marshal(err) } // EmitHumanReadable returns a human readable error message func (err Error) EmitHumanReadable() string { var errorBuffer bytes.Buffer fmt.Fprintln(&errorBuffer, err.ErrorMessage) for i, entry := range err.Stack { prettyData, _ := json.Marshal(entry.Data) fmt.Fprintln(&errorBuffer, "-", i, entry.Host+":"+entry.File+":"+strconv.Itoa(entry.Line)+" "+entry.Func+"():", string(prettyData)) } return string(errorBuffer.Bytes()) } // Emits the original error message func (err Error) Error() string { return err.EmitHumanReadable() } func init() { _, iodineFile, _, _ := runtime.Caller(0) iodineFile = path.Dir(iodineFile) // trim iodine.go iodineFile = path.Dir(iodineFile) // trim iodine iodineFile = path.Dir(iodineFile) // trim minio gopath = path.Dir(iodineFile) + "/" // trim github.com }