stacktrace/stacktrace.go (99 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you 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 stacktrace // import "go.elastic.co/apm/v2/stacktrace" import ( "runtime" "strings" ) //go:generate /bin/bash generate_library.bash std .. // AppendStacktrace appends at most n entries to frames, // skipping skip frames starting with AppendStacktrace, // and returns the extended slice. If n is negative, then // all stack frames will be appended. // // See RuntimeFrame for information on what details are included. func AppendStacktrace(frames []Frame, skip, n int) []Frame { if n == 0 { return frames } var pc []uintptr if n > 0 && n <= 10 { pc = make([]uintptr, n) pc = pc[:runtime.Callers(skip+1, pc)] } else { // n is negative or > 10, allocate space for 10 // and make repeated calls to runtime.Callers // until we've got all the frames or reached n. pc = make([]uintptr, 10) m := 0 for { m += runtime.Callers(skip+m+1, pc[m:]) if m < len(pc) || m == n { pc = pc[:m] break } // Extend pc's length, ensuring its length // extends to its new capacity to minimise // the number of calls to runtime.Callers. pc = append(pc, 0) for len(pc) < cap(pc) { pc = append(pc, 0) } } } return AppendCallerFrames(frames, pc, n) } // AppendCallerFrames appends to n frames for the PCs in callers, // and returns the extended slice. If n is negative, all available // frames will be added. Multiple frames may exist for the same // caller/PC in the case of function call inlining. // // See RuntimeFrame for information on what details are included. func AppendCallerFrames(frames []Frame, callers []uintptr, n int) []Frame { if len(callers) == 0 { return frames } runtimeFrames := runtime.CallersFrames(callers) for i := 0; n < 0 || i < n; i++ { runtimeFrame, more := runtimeFrames.Next() frames = append(frames, RuntimeFrame(runtimeFrame)) if !more { break } } return frames } // RuntimeFrame returns a Frame based on the given runtime.Frame. // // The resulting Frame will have the file path, package-qualified // function name, and line number set. The function name can be // split using SplitFunctionName, and the absolute path of the // file and its base name can be determined using standard filepath // functions. func RuntimeFrame(in runtime.Frame) Frame { return Frame{ File: in.File, Function: in.Function, Line: in.Line, } } // SplitFunctionName splits the function name as formatted in // runtime.Frame.Function, and returns the package path and // function name components. func SplitFunctionName(in string) (packagePath, function string) { function = in if function == "" { return "", "" } // The last part of a package path will always have "." // encoded as "%2e", so we can pick off the package path // by finding the last part of the package path, and then // the proceeding ".". // // Unexported method names may contain the package path. // In these cases, the method receiver will be enclosed // in parentheses, so we can treat that as the start of // the function name. sep := strings.Index(function, ".(") if sep >= 0 { packagePath = unescape(function[:sep]) function = function[sep+1:] } else { offset := 0 if sep := strings.LastIndex(function, "/"); sep >= 0 { offset = sep } if sep := strings.IndexRune(function[offset+1:], '.'); sep >= 0 { packagePath = unescape(function[:offset+1+sep]) function = function[offset+1+sep+1:] } } return packagePath, function } func unescape(s string) string { var n int for i := 0; i < len(s); i++ { if s[i] == '%' { n++ } } if n == 0 { return s } bytes := make([]byte, 0, len(s)-2*n) for i := 0; i < len(s); i++ { b := s[i] if b == '%' && i+2 < len(s) { b = fromhex(s[i+1])<<4 | fromhex(s[i+2]) i += 2 } bytes = append(bytes, b) } return string(bytes) } func fromhex(b byte) byte { if b >= 'a' { return 10 + b - 'a' } return b - '0' }