示例#1
0
 /// <summary>
 /// Construct a processed callstack.
 /// </summary>
 /// <param name="CurrentCrash"></param>
 public CallStackContainer(Crash CurrentCrash)
 {
     using (FAutoScopedLogTimer LogTimer = new FAutoScopedLogTimer(this.GetType().ToString() + "(CrashId=" + CurrentCrash.Id + ")"))
     {
         ParseCallStack(CurrentCrash);
     }
 }
示例#2
0
        //.*?\(.*?\).*?\[.*?\]

        /// <summary>
        /// Parse a raw callstack into a pattern
        /// </summary>
        /// <param name="CurrentCrash">The crash with a raw callstack to parse.</param>
        private void ParseCallStack(Crash CurrentCrash)
        {
            // Everything is disabled by default
            bDisplayUnformattedCallStack = false;
            bDisplayModuleNames          = false;
            bDisplayFunctionNames        = false;
            bDisplayFileNames            = false;
            bDisplayFilePathNames        = false;

            bool   bSkipping      = false;
            string LineToSkipUpto = "";

            switch (CurrentCrash.CrashType)
            {
            case 2:
                bSkipping      = true;
                LineToSkipUpto = "FDebug::AssertFailed";
                break;

            case 3:
                bSkipping      = true;
                LineToSkipUpto = "FDebug::";
                break;
            }

            if (string.IsNullOrEmpty(CurrentCrash.RawCallStack))
            {
                return;
            }

            CallStackEntries.Clear();

            // Store off a pre split array of call stack lines
            string[] RawCallStackLines = CurrentCrash.RawCallStack.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);

            int Middle = RawCallStackLines.Length / 2;

            // Support older callstacks uploaded before UE4 upgrade
            if (!NewCallstackFormat.Match(RawCallStackLines[Middle]).Success)
            {
                foreach (string CurrentLine in RawCallStackLines)
                {
                    // Exit if we've hit the max number of lines we want
                    if (CallStackEntries.Count >= MaxLinesToParse)
                    {
                        break;
                    }

                    ParseUE3FormatCallstackLine(CurrentLine);
                }
                return;
            }

            foreach (string CurrentLine in RawCallStackLines)
            {
                // Exit if we've hit the max number of lines we want
                if (CallStackEntries.Count >= MaxLinesToParse)
                {
                    break;
                }

                if (bSkipping)
                {
                    if (CurrentLine.Contains(LineToSkipUpto))
                    {
                        bSkipping = false;
                    }
                }

                if (bSkipping)
                {
                    continue;
                }

                string ModuleName = "<Unknown>";
                string FuncName   = "<Unknown>";
                string FilePath   = "";
                int    LineNumber = 0;

                //
                // Generic sample line "UE4_Engine!UEngine::Exec() {+ 21105 bytes} [d:\depot\ue4\engine\source\runtime\engine\private\unrealengine.cpp:2777]"
                //
                // Mac
                // thread_start()  Address = 0x7fff87ae141d (filename not found) [in libsystem_pthread.dylib]
                //
                // Linux
                // Unknown!AFortPlayerController::execServerSaveLoadoutData(FFrame&, void*) + some bytes

                int ModuleSeparator    = CurrentLine.IndexOf('!');
                int PlusOffset         = CurrentLine.IndexOf(" + ");
                int OpenFuncSymbol     = CurrentLine.IndexOf('(');
                int CloseFuncSymbol    = CurrentLine.IndexOf(')');
                int OpenBracketOffset  = CurrentLine.IndexOf('[');
                int CloseBracketOffset = CurrentLine.LastIndexOf(']');

                int MacModuleStart = CurrentLine.IndexOf("[in ");
                int MacModuleEnd   = MacModuleStart > 0 ? CurrentLine.IndexOf("]", MacModuleStart) : 0;

                bool bLinux   = CurrentCrash.PlatformName.Contains("Linux");
                bool bMac     = CurrentCrash.PlatformName.Contains("Mac");
                bool bWindows = CurrentCrash.PlatformName.Contains("Windows");


                // Parse out the juicy info from the line of the callstack
                if (ModuleSeparator > 0)
                {
                    ModuleName = CurrentLine.Substring(0, ModuleSeparator).Trim();
                    if (OpenFuncSymbol > ModuleSeparator && CloseFuncSymbol > OpenFuncSymbol)
                    {
                        // Grab the function name if it exists
                        FuncName  = CurrentLine.Substring(ModuleSeparator + 1, OpenFuncSymbol - ModuleSeparator - 1).Trim();
                        FuncName += "()";

                        // Grab the source file
                        if (OpenBracketOffset > CloseFuncSymbol && CloseBracketOffset > OpenBracketOffset && (bWindows || bLinux))
                        {
                            string FileLinePath = CurrentLine.Substring(OpenBracketOffset + 1, CloseBracketOffset - OpenBracketOffset - 1).Trim();

                            FilePath = FileLinePath.TrimEnd("0123456789:".ToCharArray());

                            if (FileLinePath.Length > FilePath.Length + 1)
                            {
                                int SourceLine = 0;
                                if (int.TryParse(FileLinePath.Substring(FilePath.Length + 1), out SourceLine))
                                {
                                    LineNumber = SourceLine;
                                }
                            }
                        }
                    }
                }
                else if (bWindows)
                {
                    // Grab the module name if there is no function name
                    int WhiteSpacePos = CurrentLine.IndexOf(' ');
                    ModuleName = WhiteSpacePos > 0 ? CurrentLine.Substring(0, WhiteSpacePos) : CurrentLine;
                }

                if (bMac && MacModuleStart > 0 && MacModuleEnd > 0)
                {
                    int AddressOffset     = CurrentLine.IndexOf("Address =");
                    int OpenFuncSymbolMac = AddressOffset > 0 ? CurrentLine.Substring(0, AddressOffset).LastIndexOf('(') : 0;
                    if (OpenFuncSymbolMac > 0)
                    {
                        FuncName  = CurrentLine.Substring(0, OpenFuncSymbolMac).Trim();
                        FuncName += "()";
                    }

                    ModuleName = CurrentLine.Substring(MacModuleStart + 3, MacModuleEnd - MacModuleStart - 3).Trim();
                }

                // Remove callstack entries that match any of these functions.
                var FuncsToRemove = new HashSet <string>(new string[]
                {
                    "RaiseException",
                    "FDebug::",
                    "Error::Serialize",
                    "FOutputDevice::Logf",
                    "FMsg::Logf",
                    "ReportCrash",
                    "NewReportEnsure",
                    "EngineCrashHandler",
                    "FLinuxPlatformStackWalk::CaptureStackBackTrac",
                    "FGenericPlatformStackWalk::StackWalkAndDump",
                    "FLinuxCrashContext::CaptureStackTrace",
                    "CommonLinuxCrashHandler",
                    // Generic crash handler for all platforms
                });

                bool Contains = FuncsToRemove.Contains(FuncName, new CustomFuncComparer());
                if (!Contains)
                {
                    CallStackEntries.Add(new CallStackEntry(CurrentLine, ModuleName, FilePath, FuncName, LineNumber));
                }
            }
        }
	    /// <summary>
	    /// Create call stack pattern and either insert into database or match to existing.
	    /// Update Associate Buggs.
	    /// </summary>
	    /// <param name="newCrash"></param>
	    private void BuildPattern(Crash newCrash)
	    {
	        var callstack = new CallStackContainer(newCrash);
	        newCrash.Module = callstack.GetModuleName();
	        if (newCrash.PatternId == null)
	        {
	            var patternList = new List<string>();

	            try
	            {
	                foreach (var entry in callstack.CallStackEntries.Take(CallStackContainer.MaxLinesToParse))
	                {
	                    FunctionCall currentFunctionCall;

	                    var csEntry = entry;
	                    if (_unitOfWork.FunctionRepository.Any(f => f.Call == csEntry.FunctionName))
	                    {
                            currentFunctionCall = _unitOfWork.FunctionRepository.First(f => f.Call == csEntry.FunctionName);
	                    }
	                    else
	                    {
	                        currentFunctionCall = new FunctionCall { Call = csEntry.FunctionName };
	                        _unitOfWork.FunctionRepository.Save(currentFunctionCall);
	                        _unitOfWork.Save();
	                    }

	                    patternList.Add(currentFunctionCall.Id.ToString());
	                }

	                newCrash.Pattern = string.Join("+", patternList);
	            }
	            catch (Exception ex)
	            {
                    var messageBuilder = new StringBuilder();
                    FLogger.Global.WriteException("Build Pattern exception: " + ex.Message.ToString(CultureInfo.InvariantCulture));
                    messageBuilder.AppendLine("Exception was:");
                    messageBuilder.AppendLine(ex.ToString());
                    while (ex.InnerException != null)
                    {
                        ex = ex.InnerException;
                        messageBuilder.AppendLine(ex.ToString());
                    }

                    _slackWriter.Write("Build Pattern Exception : " + ex.Message.ToString(CultureInfo.InvariantCulture));
                    throw;
	            }
	        }
	    }
	    /// <summary>
	    /// Create a new crash data model object and insert it into the database
	    /// </summary>
	    /// <param name="description"></param>
	    /// <returns></returns>
	    private Crash CreateCrash(CrashDescription description)
	    {
	        var newCrash = new Crash
	        {
	            Branch = description.BranchName,
	            BaseDir = description.BaseDir,
	            BuildVersion = description.EngineVersion,
                EngineVersion = description.BuildVersion,
	            ChangeListVersion = description.BuiltFromCL.ToString(),
	            CommandLine = description.CommandLine,
	            EngineMode = description.EngineMode,
	            ComputerName = description.MachineGuid
	        };

	        //if there's a valid username assign the associated UserNameId else use "anonymous".
	        var userName = (!string.IsNullOrEmpty(description.UserName)) ? description.UserName : UserNameAnonymous;
	        var user = _unitOfWork.UserRepository.GetByUserName(userName);

	        if (user != null)
	            newCrash.UserNameId = user.Id;
	        else
	        {
	            newCrash.User = new User() {UserName = description.UserName, UserGroupId = 5};
	        }

	        //If there's a valid EpicAccountId assign that.
	        if (!string.IsNullOrEmpty(description.EpicAccountId))
	        {
	            newCrash.EpicAccountId = description.EpicAccountId;
	        }

	        newCrash.Description = "";
	        if (description.UserDescription != null)
	        {
	            newCrash.Description = string.Join(Environment.NewLine, description.UserDescription);
	        }

	        newCrash.EngineMode = description.EngineMode;
	        newCrash.GameName = description.GameName;
	        newCrash.LanguageExt = description.Language; //Converted by the crash process.
	        newCrash.PlatformName = description.Platform;

	        if (description.ErrorMessage != null)
	        {
	            newCrash.Summary = string.Join("\n", description.ErrorMessage);
	        }

	        if (description.CallStack != null)
	        {
	            newCrash.RawCallStack = string.Join("\n", description.CallStack);
	        }

	        if (description.SourceContext != null)
	        {
	            newCrash.SourceContext = string.Join("\n", description.SourceContext);
	        }

	        newCrash.TimeOfCrash = description.TimeofCrash;
	        newCrash.Processed = description.bAllowToBeContacted;

	        newCrash.Jira = "";
	        newCrash.FixedChangeList = "";
	        newCrash.ProcessFailed = description.bProcessorFailed;

	        // Set the crash type
	        newCrash.CrashType = 1;

	        //if we have a crash type set the crash type
	        if (!string.IsNullOrEmpty(description.CrashType))
	        {
	            switch (description.CrashType.ToLower())
	            {
	                case "crash":
	                    newCrash.CrashType = 1;
	                    break;
	                case "assert":
	                    newCrash.CrashType = 2;
	                    break;
	                case "ensure":
	                    newCrash.CrashType = 3;
	                    break;
	                case "":
	                case null:
	                default:
	                    newCrash.CrashType = 1;
	                    break;
	            }
	        }
	        else //else fall back to the old behavior and try to determine type from RawCallStack
	        {
	            if (newCrash.RawCallStack != null)
	            {
	                if (newCrash.RawCallStack.Contains("FDebug::AssertFailed"))
	                {
	                    newCrash.CrashType = 2;
	                }
	                else if (newCrash.RawCallStack.Contains("FDebug::Ensure"))
	                {
	                    newCrash.CrashType = 3;
	                }
	                else if (newCrash.RawCallStack.Contains("FDebug::OptionallyLogFormattedEnsureMessageReturningFalse"))
	                {
	                    newCrash.CrashType = 3;
	                }
	                else if (newCrash.RawCallStack.Contains("NewReportEnsure"))
	                {
	                    newCrash.CrashType = 3;
	                }
	            }
	        }

	        // As we're adding it, the status is always new
	        newCrash.Status = "New";

	        /*
                Unused Crashes' fields.
	 
                Title				nchar(20)		
                Selected			bit				
                Version				int				
                AutoReporterID		int				
                Processed			bit	-> renamed to AllowToBeContacted			
                HasDiagnosticsFile	bit	always true		
                HasNewLogFile		bit				
                HasMetaData			bit	always true
            */

	        // Set the unused fields to the default values.
	        //NewCrash.Title = "";			removed from dbml
	        //NewCrash.Selected = false;	removed from dbml
	        //NewCrash.Version = 4;			removed from dbml
	        //NewCrash.AutoReporterID = 0;	removed from dbml
	        //NewCrash.HasNewLogFile = false;removed from dbml

	        //NewCrash.HasDiagnosticsFile = true;
	        //NewCrash.HasMetaData = true;
            newCrash.UserActivityHint = description.UserActivityHint;
            
            BuildPattern(newCrash);

            if(newCrash.CommandLine == null)
                newCrash.CommandLine = "";

            _unitOfWork.Dispose();
            _unitOfWork = new UnitOfWork(new CrashReportEntities());
	        var callStackRepository = _unitOfWork.CallstackRepository;

	        try
	        {
	            var crashRepo = _unitOfWork.CrashRepository;
                //if we don't have any callstack data then insert the crash and return
	            if (string.IsNullOrEmpty(newCrash.Pattern))
	            {
	                crashRepo.Save(newCrash);
                    _unitOfWork.Save();
	                return newCrash;
	            }

                //If this isn't a new pattern then link it to our crash data model
                if (callStackRepository.Any(data => data.Pattern == newCrash.Pattern))
	            {
                    var callstackPattern = callStackRepository.First(data => data.Pattern == newCrash.Pattern);
	                newCrash.PatternId = callstackPattern.id;
	            }
	            else
	            {
	                //if this is a new callstack pattern then insert into data model and create a new bugg.
	                var callstackPattern = new CallStackPattern { Pattern = newCrash.Pattern };
	                callStackRepository.Save(callstackPattern);
                    _unitOfWork.Save();
	                newCrash.PatternId = callstackPattern.id;
	            }

                //Mask out the line number and File path from our error message.
                var errorMessageString = description.ErrorMessage != null ? String.Join("", description.ErrorMessage) : "";

                //Create our masking regular expressions
                var fileRegex = new Regex(@"(\[File:).*?(])");//Match the filename out the file name
                var lineRegex = new Regex(@"(\[Line:).*?(])");//Match the line no.

                /**
                 * Regex to match ints of two characters or longer
                 * First term ((?<=\s)|(-)) : Positive look behind, match if preceeded by whitespace or if first character is '-'
                 * Second term (\d{3,}) match three or more decimal chracters in a row.
                 * Third term (?=(\s|$)) positive look ahead match if followed by whitespace or end of line/file.
                 */
                var intRegex = new Regex(@"-?\d{3,}");
                
                /**
                 * Regular expression for masking out floats
                 */
                var floatRegex = new Regex(@"-?\d+\.\d+");

                /**
                 * Regular expression for masking out hexadecimal numbers
                 */
                var hexRegex = new Regex(@"0x[\da-fA-F]+");

                //mask out terms matches by our regex's
                var trimmedError = fileRegex.Replace(errorMessageString, "");
                trimmedError = lineRegex.Replace(trimmedError, "");
                trimmedError = floatRegex.Replace(trimmedError, "");
                trimmedError = hexRegex.Replace(trimmedError, "");
                trimmedError = intRegex.Replace(trimmedError, "");
                
                //Check to see if the masked error message is unique
	            ErrorMessage errorMessage = null;
                if (_unitOfWork.ErrorMessageRepository.Any(data => data.Message.Contains(trimmedError)))
                {
                    errorMessage =
                        _unitOfWork.ErrorMessageRepository.First(data => data.Message.Contains(trimmedError));
                }
                else
                {
                    //if it's a new message then add it to the database.
                    errorMessage = new ErrorMessage() { Message = trimmedError };
                    _unitOfWork.ErrorMessageRepository.Save(errorMessage);
                    _unitOfWork.Save();
                }

	            //Check for an existing bugg with this pattern and error message / no error message
                if ( _unitOfWork.BuggRepository.Any(data =>
                    (data.PatternId == newCrash.PatternId || data.Pattern == newCrash.Pattern) && (newCrash.CrashType == 3 ||
                    (data.ErrorMessageId == errorMessage.Id || data.ErrorMessageId == null)) ))
                {
                    //if a bugg exists for this pattern update the bugg data
                    var bugg = _unitOfWork.BuggRepository.First(data => data.PatternId == newCrash.PatternId) ??
                                _unitOfWork.BuggRepository.First(data => data.Pattern == newCrash.Pattern);
                    bugg.PatternId = newCrash.PatternId;

                    if (newCrash.CrashType != 3)
                    {
                        bugg.CrashType = newCrash.CrashType;
                        bugg.ErrorMessageId = errorMessage.Id;
                    }

	                //also update the bugg data while we're here
	                bugg.TimeOfLastCrash = newCrash.TimeOfCrash;
	                
	                if (String.Compare(newCrash.BuildVersion, bugg.BuildVersion,
	                        StringComparison.Ordinal) != 1)
	                    bugg.BuildVersion = newCrash.BuildVersion;

                    _unitOfWork.Save();

	                //if a bugg exists update this crash from the bugg
	                //buggs are authoritative in this case
	                newCrash.Buggs.Add(bugg);
	                newCrash.Jira = bugg.TTPID;
	                newCrash.FixedChangeList = bugg.FixedChangeList;
	                newCrash.Status = bugg.Status;
	                _unitOfWork.CrashRepository.Save(newCrash);
	                _unitOfWork.Save();
	            }
	            else
	            {
	                //if there's no bugg for this pattern create a new bugg and insert into the data store.
	                var bugg = new Bugg();
	                bugg.TimeOfFirstCrash = newCrash.TimeOfCrash;
	                bugg.TimeOfLastCrash = newCrash.TimeOfCrash;
	                bugg.TTPID = newCrash.Jira;
                    bugg.Pattern = newCrash.Pattern;
	                bugg.PatternId = newCrash.PatternId;
	                bugg.NumberOfCrashes = 1;
	                bugg.NumberOfUsers = 1;
	                bugg.NumberOfUniqueMachines = 1;
	                bugg.BuildVersion = newCrash.BuildVersion;
	                bugg.CrashType = newCrash.CrashType;
	                bugg.Status = newCrash.Status;
                    bugg.FixedChangeList = newCrash.FixedChangeList;
	                bugg.ErrorMessageId = errorMessage.Id;
                    newCrash.Buggs.Add(bugg);
                    _unitOfWork.BuggRepository.Save(bugg);
                    _unitOfWork.CrashRepository.Save(newCrash);
                    _unitOfWork.Save();
	            }
	        }
            catch (DbEntityValidationException dbentEx)
            {
                var messageBuilder = new StringBuilder();
                messageBuilder.AppendLine("Db Entity Validation Exception Exception was:");
                messageBuilder.AppendLine(dbentEx.ToString());

                var innerEx = dbentEx.InnerException;
                while (innerEx != null)
                {
                    messageBuilder.AppendLine("Inner Exception : " + innerEx.Message);
                    innerEx = innerEx.InnerException;
                }

                if (dbentEx.EntityValidationErrors != null)
                {
                    messageBuilder.AppendLine("Validation Errors : ");
                    foreach (var valErr in dbentEx.EntityValidationErrors)
                    {
                        messageBuilder.AppendLine(valErr.ValidationErrors.Select(data => data.ErrorMessage).Aggregate((current, next) => current + "; /n" + next));
                    }
                }
                FLogger.Global.WriteException(messageBuilder.ToString());
            }
	        catch (Exception ex)
	        {
                var messageBuilder = new StringBuilder();
                messageBuilder.AppendLine("Create Crash Exception : ");
                messageBuilder.AppendLine(ex.Message.ToString());
                var innerEx = ex.InnerException;
                while (innerEx != null)
                {
                    messageBuilder.AppendLine("Inner Exception : " + innerEx.Message);
                    innerEx = innerEx.InnerException;
                }

	            FLogger.Global.WriteException("Create Crash Exception : " +
	                                          messageBuilder.ToString());
                _slackWriter.Write("Create Crash Exception : " + messageBuilder.ToString());
	            throw;
	        }

            return newCrash;
	    }