func getOrBuildFrames(err error, parent error, skip int, tracer StackTracerFunc) []runtime.Frame {
	if st, ok := tracer(err); ok && st != nil {
		return st
	}
	if _, ok := tracer(parent); ok {
		return nil
	}
	return getCallersFrames(1 + skip)
}
getOrBuildFrames gets stack frames from errors that provide one of their own
otherwise, it builds a new stack trace. It returns the stack frames if the error
is of a compatible type. If the error is not, but the parent error is, it assumes
the parent error will be processed later and therefore returns nil.
package rollbar
import (
	"context"
	"net/http"
	"net/url"
	"reflect"
	"regexp"
	"runtime"
	"strings"
	"time"
)
func buildBody(ctx context.Context, configuration configuration, diagnostic diagnostic,
	level, title string, extras map[string]interface{}) map[string]interface{} {
	timestamp := time.Now().Unix()
	data := map[string]interface{}{
		"environment":  configuration.environment,
		"title":        title,
		"level":        level,
		"timestamp":    timestamp,
		"platform":     configuration.platform,
		"language":     "go",
		"code_version": configuration.codeVersion,
		"server": map[string]interface{}{
			"host": configuration.serverHost,
			"root": configuration.serverRoot,
		},
		"notifier": map[string]interface{}{
			"name":    NAME,
			"version": VERSION,
			"diagnostic": map[string]interface{}{
				"languageVersion":   diagnostic.languageVersion,
				"configuredOptions": buildConfiguredOptions(configuration),
			},
		},
	}
	custom := buildCustom(configuration.custom, extras)
	if custom != nil {
		data["custom"] = custom
	}
	person, ok := PersonFromContext(ctx)
	if !ok {
		person = &configuration.person
	}
	if person.Id != "" {
		data["person"] = map[string]string{
			"id":       person.Id,
			"username": person.Username,
			"email":    person.Email,
		}
	}
	return map[string]interface{}{
		"data": data,
	}
}
func buildCustom(custom map[string]interface{}, extras map[string]interface{}) map[string]interface{} {
	if custom == nil && extras == nil {
		return nil
	}
	m := map[string]interface{}{}
	for k, v := range custom {
		m[k] = v
	}
	for k, v := range extras {
		m[k] = v
	}
	return m
}
func buildConfiguredOptions(configuration configuration) map[string]interface{} {
	return map[string]interface{}{
		"environment":    configuration.environment,
		"endpoint":       configuration.endpoint,
		"platform":       configuration.platform,
		"codeVersion":    configuration.codeVersion,
		"serverHost":     configuration.serverHost,
		"serverRoot":     configuration.serverRoot,
		"fingerprint":    configuration.fingerprint,
		"scrubHeaders":   configuration.scrubHeaders,
		"scrubFields":    configuration.scrubFields,
		"transform":      functionToString(configuration.transform),
		"unwrapper":      functionToString(configuration.unwrapper),
		"stackTracer":    functionToString(configuration.stackTracer),
		"checkIgnore":    functionToString(configuration.checkIgnore),
		"captureIp":      configuration.captureIp,
		"itemsPerMinute": configuration.itemsPerMinute,
		"person": map[string]string{
			"Id":       configuration.person.Id,
			"Username": configuration.person.Username,
			"Email":    configuration.person.Email,
		},
	}
}
func addErrorToBody(configuration configuration, body map[string]interface{}, err error, skip int, telemetry []interface{}) map[string]interface{} {
	data := body["data"].(map[string]interface{})
	errBody, fingerprint := errorBody(configuration, err, skip)
	dataBody := errBody
	dataBody["telemetry"] = telemetry
	data["body"] = dataBody
	if configuration.fingerprint {
		data["fingerprint"] = fingerprint
	}
	return data
}
func requestDetails(configuration configuration, r *http.Request) map[string]interface{} {
	cleanQuery := filterParams(configuration.scrubFields, r.URL.Query())
	specialHeaders := map[string]struct{}{
		"Content-Type": struct{}{},
	}
	return map[string]interface{}{
		"url":     r.URL.String(),
		"method":  r.Method,
		"headers": filterFlatten(configuration.scrubHeaders, r.Header, specialHeaders),
		"query_string": url.Values(cleanQuery).Encode(),
		"GET":          flattenValues(cleanQuery),
		"POST":    filterFlatten(configuration.scrubFields, r.Form, nil),
		"user_ip": filterIp(remoteIP(r), configuration.captureIp),
	}
}
func remoteIP(req *http.Request) string {
	realIP := req.Header.Get("X-Real-IP")
	if realIP != "" {
		return realIP
	}
	forwardedIPs := req.Header.Get("X-Forwarded-For")
	if forwardedIPs != "" {
		ips := strings.Split(forwardedIPs, ", ")
		return ips[0]
	}
	return req.RemoteAddr
}
func filterFlatten(pattern *regexp.Regexp, values map[string][]string, specialKeys map[string]struct{}) map[string]interface{} {
	result := make(map[string]interface{})
	for k, v := range values {
		switch _, special := specialKeys[k]; {
		case pattern.Match([]byte(k)):
			result[k] = FILTERED
		case special || len(v) == 1:
			result[k] = v[0]
		default:
			result[k] = v
		}
	}
	return result
}
func filterParams(pattern *regexp.Regexp, values map[string][]string) map[string][]string {
	for key := range values {
		if pattern.Match([]byte(key)) {
			values[key] = []string{FILTERED}
		}
	}
	return values
}
func flattenValues(values map[string][]string) map[string]interface{} {
	result := make(map[string]interface{})
	for k, v := range values {
		if len(v) == 1 {
			result[k] = v[0]
		} else {
			result[k] = v
		}
	}
	return result
}
func filterIp(ip string, captureIp captureIp) string {
	switch captureIp {
	case CaptureIpFull:
		return ip
	case CaptureIpAnonymize:
		if strings.Contains(ip, ".") {
			parts := strings.Split(ip, ".")
			parts[len(parts)-1] = "0"
			return strings.Join(parts, ".")
		}
		if strings.Contains(ip, ":") {
			parts := strings.Split(ip, ":")
			if len(parts) > 2 {
				parts = parts[0:3]
				parts = append(parts, "0000:0000:0000:0000:0000")
				return strings.Join(parts, ":")
			}
			return ip
		}
		return ip
	case CaptureIpNone:
		return ""
	default:
		return ""
	}
}
func errorBody(configuration configuration, err error, skip int) (map[string]interface{}, string) {
	var parent error
	traceChain := []map[string]interface{}{}
	fingerprint := ""
	for {
		stack := buildStack(getOrBuildFrames(err, parent, 1+skip, configuration.stackTracer))
		traceChain = append(traceChain, buildTrace(err, stack))
		if configuration.fingerprint {
			fingerprint = fingerprint + stack.Fingerprint()
		}
		parent = err
		err = configuration.unwrapper(err)
		if err == nil {
			break
		}
	}
	errBody := map[string]interface{}{"trace_chain": traceChain}
	return errBody, fingerprint
}
func buildTrace(err error, stack stack) map[string]interface{} {
	message := nilErrTitle
	if err != nil {
		message = err.Error()
	}
	return map[string]interface{}{
		"frames": stack,
		"exception": map[string]interface{}{
			"class":   errorClass(err),
			"message": message,
		},
	}
}