201 lines
5.8 KiB
Go
201 lines
5.8 KiB
Go
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"go.uber.org/zap"
|
|
"log-enricher/configs"
|
|
"log-enricher/models/es"
|
|
dbPackage "log-enricher/pkg/db"
|
|
"log-enricher/pkg/encoder"
|
|
"log-enricher/pkg/log"
|
|
"log-enricher/pkg/symbolicator"
|
|
"path"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type ErrorProcessor struct {
|
|
logger *log.Logger
|
|
elasticSearchClient *dbPackage.ElasticSearchClient
|
|
sourceMapFetcher *SourceMapService
|
|
awsConfig configs.AwsConfig
|
|
}
|
|
|
|
type Frame struct {
|
|
Filename string `json:"filename"`
|
|
Function string `json:"function"`
|
|
InApp bool `json:"in_app"`
|
|
Lineno int `json:"lineno"`
|
|
Colno int `json:"colno"`
|
|
SourceMapPath string `json:"source_map_path,omitempty"`
|
|
}
|
|
|
|
type Stacktrace struct {
|
|
Frames []Frame `json:"frames"`
|
|
}
|
|
|
|
type MechanismData struct {
|
|
Function string `json:"function"`
|
|
Handler string `json:"handler"`
|
|
Target string `json:"target"`
|
|
}
|
|
|
|
type Mechanism struct {
|
|
Type string `json:"type"`
|
|
Handled bool `json:"handled"`
|
|
Data MechanismData `json:"data"`
|
|
}
|
|
|
|
type Extra struct {
|
|
ReleaseId string `json:"release_id"`
|
|
}
|
|
|
|
type Exception struct {
|
|
Type string `json:"type"`
|
|
Value string `json:"value"`
|
|
Stacktrace Stacktrace `json:"stacktrace"`
|
|
Mechanism Mechanism `json:"mechanism"`
|
|
ProjectId string `json:"project_id,omitempty"`
|
|
ReleaseId string `json:"release_id,omitempty"`
|
|
Breadcrumbs interface{} `json:"breadcrumbs,omitempty"`
|
|
Extra interface{} `json:"extra,omitempty"`
|
|
Request interface{} `json:"request,omitempty"`
|
|
}
|
|
|
|
func NewErrorProcessor(logger *log.Logger, elasticSearchClient *dbPackage.ElasticSearchClient, sourceMapFetcherService *SourceMapService, awsConfig configs.AwsConfig) *ErrorProcessor {
|
|
return &ErrorProcessor{
|
|
logger: logger,
|
|
elasticSearchClient: elasticSearchClient,
|
|
sourceMapFetcher: sourceMapFetcherService,
|
|
awsConfig: awsConfig,
|
|
}
|
|
|
|
}
|
|
|
|
func extractFilename(url string) string {
|
|
// Remove any trailing characters you don't want (in case there are any)
|
|
url = strings.Trim(url, `"`)
|
|
|
|
// Check if the URL starts with "app:///" and remove the scheme if present this is to parse react native file names
|
|
if strings.HasPrefix(url, "app:///") {
|
|
var stringWithprefixRemoved = strings.TrimPrefix(url, "app:///")
|
|
var stringWithSuffixRemoved = strings.Split(stringWithprefixRemoved, "?")[0]
|
|
return stringWithSuffixRemoved + ".map"
|
|
|
|
}
|
|
// Extract the base (filename) from the URL path
|
|
filename := path.Base(url)
|
|
return filename + ".map"
|
|
}
|
|
|
|
func extractPlatform(payload Exception) string {
|
|
if len(payload.Stacktrace.Frames) == 0 {
|
|
return "WEB"
|
|
}
|
|
var url = payload.Stacktrace.Frames[0].Filename
|
|
if strings.HasPrefix(url, "app:///") {
|
|
return "REACT_NATIVE"
|
|
}
|
|
return "WEB"
|
|
}
|
|
|
|
func getSignificantStack(trace symbolicator.SymbolicatedStackTrace) string {
|
|
if len(trace.Frames) != 0 {
|
|
return trace.Frames[len(trace.Frames)-1].Token
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (ep *ErrorProcessor) ProcessError(error []byte) {
|
|
ep.logger.Info("processing error in consumer")
|
|
var payload Exception
|
|
err := json.Unmarshal(error, &payload)
|
|
var validFrames []Frame
|
|
var invalidFrames []Frame
|
|
var platform = extractPlatform(payload)
|
|
|
|
if err != nil {
|
|
ep.logger.Error("error in unmarshalling exception", zap.Error(err))
|
|
return
|
|
}
|
|
extraMap := payload.Extra.(map[string]interface{})
|
|
releaseId, ok := extraMap["release_id"].(string)
|
|
if !ok {
|
|
ep.logger.Error("Release id is not found")
|
|
return
|
|
}
|
|
|
|
//getting source map path
|
|
for i, frame := range payload.Stacktrace.Frames {
|
|
fileName := extractFilename(frame.Filename)
|
|
projectId := payload.ProjectId
|
|
frame := &payload.Stacktrace.Frames[i]
|
|
ep.logger.Info("processing frame", zap.String("filename", fileName))
|
|
ep.logger.Info("processing frame", zap.String("filename", frame.SourceMapPath))
|
|
//todo make release dynamic
|
|
sourceMapPath, err := ep.sourceMapFetcher.GetSourceMap(fileName, projectId, releaseId, ep.awsConfig.Bucket)
|
|
if err != nil {
|
|
ep.logger.Error("error occured in fetching source map skipping this frame", zap.Error(err))
|
|
invalidFrames = append(invalidFrames, *frame)
|
|
continue
|
|
}
|
|
frame.SourceMapPath = sourceMapPath
|
|
validFrames = append(validFrames, *frame)
|
|
|
|
}
|
|
payload.Stacktrace.Frames = validFrames
|
|
var frames, marshalErr = json.Marshal(&payload.Stacktrace)
|
|
|
|
if marshalErr != nil {
|
|
ep.logger.Error("unable to serilize frames", zap.Error(marshalErr))
|
|
}
|
|
var symbolicatorCommand = ""
|
|
var args = []string{""}
|
|
if platform == "REACT_NATIVE" {
|
|
symbolicatorCommand = "node"
|
|
args = []string{"bins/cybertron-symbolicator-react-native/index.js", string(frames)}
|
|
} else {
|
|
symbolicatorCommand = "bins/source-map"
|
|
args = []string{string(frames)}
|
|
}
|
|
|
|
command := &symbolicator.Command{
|
|
Cmd: symbolicatorCommand,
|
|
Args: args,
|
|
}
|
|
|
|
output, err := symbolicator.SymbolicatorClient(command)
|
|
for _, v := range invalidFrames {
|
|
output.Frames = append(output.Frames, symbolicator.SymbolicatedFrame{
|
|
Token: v.Filename,
|
|
OriginalLine: v.Lineno,
|
|
Lines: []string{"non symbolic code"},
|
|
Start: 0,
|
|
End: 0,
|
|
})
|
|
}
|
|
|
|
if err != nil {
|
|
ep.logger.Error("error occured in symobilicator client", zap.Error(err))
|
|
}
|
|
|
|
//make md5 hash of error
|
|
hash := encoder.Md5Encode(string(frames))
|
|
//creating es document
|
|
ep.logger.Info("processed document successfully saving it to elasticsearch", zap.String("hash", hash))
|
|
errorDocument := &es.ErrorDocument{
|
|
Error: payload.Value,
|
|
Title: payload.Type,
|
|
StackTrace: output.Frames,
|
|
SignificantStack: getSignificantStack(output),
|
|
ErrorHash: hash,
|
|
ProjectId: payload.ProjectId,
|
|
ReleaseVersion: releaseId,
|
|
CreatedAt: time.Now().Unix(),
|
|
Breadcrumbs: payload.Breadcrumbs,
|
|
Extra: payload.Extra,
|
|
Request: payload.Request,
|
|
}
|
|
ep.elasticSearchClient.IndexDocument(errorDocument)
|
|
}
|