public void Log(CreateLogItemRequest createLogItemRequest) { if (StartTask == null) { var exp = new InsufficientExecutionStackException("The launch wasn't scheduled for starting to add log messages."); TraceLogger.Error(exp.ToString()); throw (exp); } if (StartTask.IsFaulted || StartTask.IsCanceled) { return; } if (FinishTask == null) { lock (_lockObj) { if (_logsReporter == null) { var logRequestAmender = new LaunchLogRequestAmender(this); _logsReporter = new LogsReporter(this, _service, _extensionManager, _requestExecuter, logRequestAmender); } } _logsReporter.Log(createLogItemRequest); } }
private bool HandleAddLogCommunicationAction(ITestReporter testReporter, AddLogCommunicationMessage message) { var logRequest = new CreateLogItemRequest { Level = message.Level, Time = message.Time, Text = message.Text }; if (message.Attach != null) { logRequest.Attach = new Client.Abstractions.Responses.Attach { Name = message.Attach.Name, MimeType = message.Attach.MimeType, Data = message.Attach.Data }; } if (message.ParentScopeId != null) { testReporter = _nestedSteps[message.ParentScopeId]; } testReporter.Log(logRequest); return(true); }
public async Task <LogItemCreatedResponse> CreateAsync(CreateLogItemRequest request) { var uri = $"{ProjectName}/log"; if (request.Attach == null) { return(await PostAsJsonAsync <LogItemCreatedResponse, CreateLogItemRequest>(uri, request)); } else { var body = ModelSerializer.Serialize <List <CreateLogItemRequest> >(new List <CreateLogItemRequest> { request }); var multipartContent = new MultipartFormDataContent(); var jsonContent = new StringContent(body, Encoding.UTF8, "application/json"); multipartContent.Add(jsonContent, "json_request_part"); var byteArrayContent = new ByteArrayContent(request.Attach.Data, 0, request.Attach.Data.Length); byteArrayContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(request.Attach.MimeType); multipartContent.Add(byteArrayContent, "file", request.Attach.Name); var response = await HttpClient.PostAsync(uri, multipartContent).ConfigureAwait(false); response.VerifySuccessStatusCode(); var c = await response.Content.ReadAsStringAsync().ConfigureAwait(false); return(ModelSerializer.Deserialize <Responses>(c).LogItems[0]); } }
public void ShouldSendAsSeparateRequestPerLogWithAttachmentIncludingWithoutAttachment() { var service = new MockServiceBuilder().Build(); var logsReporter = new LogsReporter(_testReporter.Object, service.Object, _extensionManager, _requestExecuter, _logRequestAmender.Object); var withoutAttachment = new CreateLogItemRequest { Text = "a", Time = DateTime.UtcNow }; var withAttachment = new CreateLogItemRequest { Text = "a", Time = DateTime.UtcNow, Attach = new LogItemAttach() }; logsReporter.Log(withAttachment); logsReporter.Log(withoutAttachment); logsReporter.Log(withAttachment); logsReporter.Log(withoutAttachment); logsReporter.Sync(); service.Verify(s => s.LogItem.CreateAsync(It.IsAny <CreateLogItemRequest[]>()), Times.Exactly(2)); }
/// <inheritdoc/> public bool FormatLog(CreateLogItemRequest logRequest) { if (logRequest.Text != null) { var regex = new Regex("{rp#file#(.*)}"); var match = regex.Match(logRequest.Text); if (match.Success) { logRequest.Text = logRequest.Text.Replace(match.Value, ""); var filePath = match.Groups[1].Value; try { var mimeType = MimeTypes.MimeTypeMap.GetMimeType(Path.GetExtension(filePath)); logRequest.Attach = new LogItemAttach(mimeType, File.ReadAllBytes(filePath)); return(true); } catch (Exception exp) { logRequest.Text += $"{Environment.NewLine}{Environment.NewLine}Cannot fetch data by `{filePath}` path.{Environment.NewLine}{exp}"; } } } return(false); }
public void Amend(CreateLogItemRequest request) { if (request.Time < _testReporter.Info.StartTime) { request.Time = _testReporter.Info.StartTime; } request.TestItemUuid = _testReporter.Info.Uuid; }
public static CreateLogItemRequest ConvertToRequest(this ILogMessage logMessage) { if (logMessage == null) { throw new ArgumentNullException("Cannot convert nullable log message object.", nameof(logMessage)); } LogLevel logLevel; switch (logMessage.Level) { case LogMessageLevel.Debug: logLevel = LogLevel.Debug; break; case LogMessageLevel.Error: logLevel = LogLevel.Error; break; case LogMessageLevel.Fatal: logLevel = LogLevel.Fatal; break; case LogMessageLevel.Info: logLevel = LogLevel.Info; break; case LogMessageLevel.Trace: logLevel = LogLevel.Trace; break; case LogMessageLevel.Warning: logLevel = LogLevel.Warning; break; default: throw new Exception(string.Format("Unknown {0} level of log message.", logMessage.Level)); } var logRequest = new CreateLogItemRequest { Text = logMessage.Message, Time = logMessage.Time, Level = logLevel }; if (logMessage.Attachment != null) { logRequest.Attach = new LogItemAttach { MimeType = logMessage.Attachment.MimeType, Data = logMessage.Attachment.Data }; } return(logRequest); }
public void Amend(CreateLogItemRequest request) { if (request.Time < _launchReporter.Info.StartTime) { request.Time = _launchReporter.Info.StartTime; } request.LaunchUuid = _launchReporter.Info.Uuid; }
public void ShouldNotFormatNullBase64String() { var formatter = new Base64LogFormatter(); var logRequest = new CreateLogItemRequest(); var isHandled = formatter.FormatLog(logRequest); isHandled.Should().BeFalse(); }
public void ShouldNotFormatEmptyString() { var formatter = new FileLogFormatter(); var logRequest = new CreateLogItemRequest() { Text = "" }; var isHandled = formatter.FormatLog(logRequest); isHandled.Should().BeFalse(); }
public void ShouldThrowFormatIncorrectBase64String() { var formatter = new Base64LogFormatter(); var incorrectBase64 = "123"; var logRequest = new CreateLogItemRequest() { Text = $"{{rp#base64#image/png#{incorrectBase64}}}" }; formatter.Invoking(f => f.FormatLog(logRequest)).Should().Throw <Exception>(); }
public void ShouldThrowFormatIncorrectFilePathString() { var formatter = new FileLogFormatter(); var incorrectFilePath = "q.w"; var logRequest = new CreateLogItemRequest() { Text = $"{{rp#file#{incorrectFilePath}}}" }; formatter.FormatLog(logRequest); logRequest.Text.Should().Contain("Cannot"); }
public async Task <LogItemCreatedResponse> CreateAsync(CreateLogItemRequest request) { var uri = $"{ProjectName}/log"; if (request.Attach == null) { return(await PostAsJsonAsync <LogItemCreatedResponse, CreateLogItemRequest>(uri, request)); } else { var results = await CreateAsync(new CreateLogItemRequest[] { request }); return(results.LogItems.First()); } }
public void Test2() { var inputException = @" System.AggregateException: One or more errors occurred. ---> System.Exception: Test at SomeMethod() in C:\some\file.cs:line 20 at SomeMethod() in C:\some\file.cs:line 21 at System.Threading.Tasks.Task`1.InnerInvoke() at System.Threading.Tasks.Task.Execute() "; var ext = new SourceBackFormatter(); var log = new CreateLogItemRequest { Level = LogLevel.Error, Text = inputException }; ext.FormatLog(log); StringAssert.Contains("AggregateException", log.Text); }
/// <summary> /// Sends log message to current test context. /// </summary> /// <param name="logRequest">Full model object for message</param> public static void Message(CreateLogItemRequest logRequest) { var message = new LogMessage(logRequest.Text); LogMessageLevel logLevel = LogMessageLevel.Info; switch (logRequest.Level) { case LogLevel.Debug: logLevel = LogMessageLevel.Debug; break; case LogLevel.Error: logLevel = LogMessageLevel.Error; break; case LogLevel.Fatal: logLevel = LogMessageLevel.Fatal; break; case LogLevel.Info: logLevel = LogMessageLevel.Info; break; case LogLevel.Trace: logLevel = LogMessageLevel.Trace; break; case LogLevel.Warning: logLevel = LogMessageLevel.Warning; break; } message.Level = logLevel; message.Time = logRequest.Time; if (logRequest.Attach != null) { message.Attachment = new LogMessageAttachment(logRequest.Attach.MimeType, logRequest.Attach.Data); } ActiveScope.Message(message); }
public void ShouldFormatBase64String() { var formatter = new Base64LogFormatter(); var data = new byte[] { 1, 2, 3 }; var base64 = Convert.ToBase64String(data); var logRequest = new CreateLogItemRequest() { Text = $"{{rp#base64#image/png#{base64}}}" }; var isHandled = formatter.FormatLog(logRequest); isHandled.Should().BeTrue(); logRequest.Attach.Should().NotBeNull(); logRequest.Attach.MimeType.Should().Be("image/png"); logRequest.Attach.Data.Should().BeEquivalentTo(data); }
/// <inheritdoc/> public bool FormatLog(CreateLogItemRequest logRequest) { if (logRequest.Text != null) { var regex = new Regex("{rp#base64#(.*)#(.*)}"); var match = regex.Match(logRequest.Text); if (match.Success) { logRequest.Text = logRequest.Text.Replace(match.Value, ""); var mimeType = match.Groups[1].Value; var bytes = Convert.FromBase64String(match.Groups[2].Value); logRequest.Attach = new LogItemAttach(mimeType, bytes); return(true); } } return(false); }
public async Task GetLogItem() { var addLogItemRequest = new CreateLogItemRequest { TestItemUuid = _fixture.TestUuid, Text = "Log1", Time = DateTime.UtcNow, Level = LogLevel.Info }; var log = await Service.LogItem.CreateAsync(addLogItemRequest); Assert.NotNull(log.Uuid); var gotLogItem = await Service.LogItem.GetAsync(log.Uuid); Assert.Equal(addLogItemRequest.Text, gotLogItem.Text); Assert.Equal(addLogItemRequest.Level, gotLogItem.Level); Assert.Equal(addLogItemRequest.Time, gotLogItem.Time); }
public void ShouldFormatFileString() { var formatter = new FileLogFormatter(); var data = "123"; var filePath = Path.GetTempPath() + Path.GetRandomFileName(); File.WriteAllText(filePath, data); var logRequest = new CreateLogItemRequest() { Text = $"{{rp#file#{filePath}}}" }; var isHandled = formatter.FormatLog(logRequest); isHandled.Should().BeTrue(); logRequest.Attach.Should().NotBeNull(); logRequest.Attach.Data.Should().BeEquivalentTo(Encoding.UTF8.GetBytes(data)); }
private void HandleAddLogCommunicationAction(ITestReporter testReporter, AddLogCommunicationMessage logMessage) { var logRequest = new CreateLogItemRequest { Text = logMessage.Text, Time = logMessage.Time, Level = logMessage.Level }; if (logMessage.Attach != null) { logRequest.Attach = new LogItemAttach(logMessage.Attach.MimeType, logMessage.Attach.Data); } if (logMessage.ParentScopeId != null) { testReporter = _nestedScopes[logMessage.ParentScopeId]; } testReporter.Log(logRequest); }
public void Log(CreateLogItemRequest logRequest) { lock (_syncObj) { _buffer.Enqueue(logRequest); var dependentTask = ProcessingTask ?? _reporter.StartTask; ProcessingTask = dependentTask.ContinueWith(async(dt) => { try { // only if parent reporter is succesfull if (!_reporter.StartTask.IsFaulted && !_reporter.StartTask.IsCanceled) { var requests = GetBufferedLogRequests(batchCapacity: BatchCapacity); if (requests.Count != 0) { foreach (var logItemRequest in requests) { _logRequestAmender.Amend(logItemRequest); foreach (var formatter in _extensionManager.LogFormatters) { formatter.FormatLog(logItemRequest); } } await _requestExecuter.ExecuteAsync(() => _service.LogItem.CreateAsync(requests.ToArray()), null).ConfigureAwait(false); } } } catch (Exception exp) { _traceLogger.Error($"Unexpected error occurred while processing buffered log requests. {exp}"); } }, TaskContinuationOptions.PreferFairness).Unwrap(); } }
public void Test1() { try { var tasks = new List <Task>(); for (int i = 0; i < 10; i++) { var t = Task.Run(() => { throw new Exception("Test"); }); tasks.Add(t); } Task.WaitAll(tasks.ToArray()); } catch (Exception exp) { var ext = new SourceBackFormatter(); var log = new CreateLogItemRequest { Level = LogLevel.Error, Text = exp.ToString() }; ext.FormatLog(log); StringAssert.Contains("throw new Exception", log.Text); } }
public bool Handle(ILogScope logScope, CreateLogItemRequest logRequest) { var communicationMessage = new AddLogCommunicationMessage() { ParentScopeId = logScope?.Id, Time = logRequest.Time, Text = logRequest.Text, Level = logRequest.Level }; if (logRequest.Attach != null) { communicationMessage.Attach = new Attach { Name = logRequest.Attach.Name, MimeType = logRequest.Attach.MimeType, Data = logRequest.Attach.Data }; } Console.WriteLine(ModelSerializer.Serialize <AddLogCommunicationMessage>(communicationMessage)); return(true); }
public async Task <LogItemCreatedResponse> CreateAsync(CreateLogItemRequest model) { return(await Task.FromResult(new LogItemCreatedResponse())); }
private void Events_TestResult(object sender, TestResultEventArgs e) { try { var innerResultsCountProperty = e.Result.Properties.FirstOrDefault(p => p.Id == "InnerResultsCount"); if (innerResultsCountProperty == null || (innerResultsCountProperty != null && (int)e.Result.GetPropertyValue(innerResultsCountProperty) == 0)) { string className; string testName; var fullName = e.Result.TestCase.FullyQualifiedName; if (e.Result.TestCase.ExecutorUri.Host == "xunit") { var testMethodName = fullName.Split('.').Last(); var testClassName = fullName.Substring(0, fullName.LastIndexOf('.')); var displayName = e.Result.TestCase.DisplayName; testName = displayName == fullName ? testMethodName : displayName.Replace($"{testClassName}.", string.Empty); className = testClassName; } else if (e.Result.TestCase.ExecutorUri.ToString().ToLower().Contains("mstest")) { testName = e.Result.DisplayName ?? e.Result.TestCase.DisplayName; var classNameProperty = e.Result.TestCase.Properties.FirstOrDefault(p => p.Id == "MSTestDiscoverer.TestClassName"); if (classNameProperty != null) { className = e.Result.TestCase.GetPropertyValue(classNameProperty).ToString(); } // else get classname from FQN (mstestadapter/v1) else { // and temporary consider testname from TestCase object instead of from Result object // Result.DisplayName: Test1 (Data Row 0) // TestCase.DisplayName Test1 // the first name is better in report, but consider the second name to identify 'className' testName = e.Result.TestCase.DisplayName ?? e.Result.DisplayName; className = fullName.Substring(0, fullName.Length - testName.Length - 1); testName = e.Result.DisplayName ?? e.Result.TestCase.DisplayName; } } else { testName = e.Result.TestCase.DisplayName ?? fullName.Split('.').Last(); className = fullName.Substring(0, fullName.Length - testName.Length - 1); } TraceLogger.Info($"ClassName: {className}, TestName: {testName}"); var rootNamespaces = _config.GetValues <string>("rootNamespaces", null); if (rootNamespaces != null) { var rootNamespace = rootNamespaces.FirstOrDefault(rns => className.StartsWith(rns)); if (rootNamespace != null) { className = className.Substring(rootNamespace.Length + 1); TraceLogger.Verbose($"Cutting '{rootNamespace}'... New ClassName is '{className}'."); } } var suiteReporter = GetOrStartSuiteNode(className, e.Result.StartTime.UtcDateTime); // find description var testDescription = e.Result.TestCase.Traits.FirstOrDefault(x => x.Name == "Description")?.Value; if (e.Result.TestCase.ExecutorUri.ToString().ToLower().Contains("mstest")) { var testProperty = e.Result.TestCase.Properties.FirstOrDefault(p => p.Id == "Description"); if (testProperty != null) { testDescription = e.Result.TestCase.GetPropertyValue(testProperty).ToString(); } } // find categories var testCategories = e.Result.TestCase.Traits.Where(t => t.Name.ToLower() == "Category".ToLower()).Select(x => x.Value).ToList(); if (e.Result.TestCase.ExecutorUri.ToString().ToLower().Contains("mstest")) { var testProperty = e.Result.TestCase.Properties.FirstOrDefault(p => p.Id == "MSTestDiscoverer.TestCategory"); if (testProperty != null) { testCategories.AddRange((string[])e.Result.TestCase.GetPropertyValue(testProperty)); } } else if (e.Result.TestCase.ExecutorUri.ToString().ToLower().Contains("nunit")) { var testProperty = e.Result.TestCase.Properties.FirstOrDefault(p => p.Id == "NUnit.TestCategory"); if (testProperty != null) { testCategories.AddRange((string[])e.Result.TestCase.GetPropertyValue(testProperty)); } } // start test node var startTestRequest = new StartTestItemRequest { Name = testName, Description = testDescription, Attributes = testCategories.Select(tc => new ItemAttribute { Value = tc }).ToList(), StartTime = e.Result.StartTime.UtcDateTime, Type = TestItemType.Step }; var testReporter = suiteReporter.StartChildTestReporter(startTestRequest); // add log messages if (e.Result.Messages != null) { foreach (var message in e.Result.Messages) { if (message.Text == null) { continue; } foreach (var line in message.Text.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)) { var handled = false; var textMessage = line; try { // SpecRun adapter adds this for output messages, just trim it for internal text messages if (line.StartsWith("-> ")) { textMessage = line.Substring(3); } var baseCommunicationMessage = Client.Converters.ModelSerializer.Deserialize <BaseCommunicationMessage>(textMessage); switch (baseCommunicationMessage.Action) { case CommunicationAction.AddLog: var addLogCommunicationMessage = Client.Converters.ModelSerializer.Deserialize <AddLogCommunicationMessage>(textMessage); handled = HandleAddLogCommunicationAction(testReporter, addLogCommunicationMessage); break; case CommunicationAction.BeginLogScope: var beginLogScopeCommunicationMessage = Client.Converters.ModelSerializer.Deserialize <BeginScopeCommunicationMessage>(textMessage); handled = HandleBeginLogScopeCommunicationAction(testReporter, beginLogScopeCommunicationMessage); break; case CommunicationAction.EndLogScope: var endLogScopeCommunicationMessage = Client.Converters.ModelSerializer.Deserialize <EndScopeCommunicationMessage>(textMessage); handled = HandleEndLogScopeCommunicationMessage(endLogScopeCommunicationMessage); break; } } catch (Exception) { } if (!handled) { // consider line output as usual user's log message testReporter.Log(new CreateLogItemRequest { Time = DateTime.UtcNow, Level = LogLevel.Info, Text = line }); } } } } if (e.Result.ErrorMessage != null) { testReporter.Log(new CreateLogItemRequest { Time = e.Result.EndTime.UtcDateTime, Level = LogLevel.Error, Text = e.Result.ErrorMessage + "\n" + e.Result.ErrorStackTrace }); } // add attachments if (e.Result.Attachments != null) { foreach (var attachmentSet in e.Result.Attachments) { foreach (var attachmentData in attachmentSet.Attachments) { var filePath = attachmentData.Uri.LocalPath; try { var attachmentLogRequest = new CreateLogItemRequest { Level = LogLevel.Info, Text = Path.GetFileName(filePath), Time = e.Result.EndTime.UtcDateTime }; var fileExtension = Path.GetExtension(filePath); byte[] bytes; using (var fileStream = File.Open(filePath, FileMode.Open, FileAccess.Read)) { using (var memoryStream = new MemoryStream()) { fileStream.CopyTo(memoryStream); bytes = memoryStream.ToArray(); } } attachmentLogRequest.Attach = new Client.Abstractions.Responses.Attach(Path.GetFileName(filePath), Shared.MimeTypes.MimeTypeMap.GetMimeType(fileExtension), bytes); testReporter.Log(attachmentLogRequest); } catch (Exception exp) { var error = $"Cannot read a content of '{filePath}' file: {exp.Message}"; testReporter.Log(new CreateLogItemRequest { Level = LogLevel.Warning, Time = e.Result.EndTime.UtcDateTime, Text = error }); TraceLogger.Error(error); } } } } // finish test var finishTestRequest = new FinishTestItemRequest { EndTime = e.Result.EndTime.UtcDateTime, Status = _statusMap[e.Result.Outcome] }; testReporter.Finish(finishTestRequest); } } catch (Exception exp) { var error = $"Unexpected exception in {nameof(Events_TestResult)}: {exp}"; TraceLogger.Error(error); Console.WriteLine(error); } }
public void FormatLog(CreateLogItemRequest logRequest) { _traceLogger.Verbose("Received a log request to format."); var handled = false; var fullMessageBuilder = Config.GetValue("Extensions:SourceBack:WithMarkdownPrefix", false) ? new StringBuilder("!!!MARKDOWN_MODE!!!") : new StringBuilder(); if (logRequest.Level == LogLevel.Error || logRequest.Level == LogLevel.Fatal) { _traceLogger.Info($"Parsing exception stacktrace in log message with {logRequest.Level} level..."); foreach (var line in logRequest.Text.Split(new string[] { Environment.NewLine, "\n" }, StringSplitOptions.None)) { _traceLogger.Verbose("Parsing line as stacktrace frame:" + Environment.NewLine + line); var lineWithoutMarkdown = line.Replace("`", @"\`").Replace("__", @"\__"); var match = Regex.Match(line, @"\s+\w+\s.*\s\w+\s(.*):\w+\s(\d+)"); if (match.Success) { var sourcePath = match.Groups[1].Value; var lineIndex = int.Parse(match.Groups[2].Value) - 1; _traceLogger.Info($"It matches stacktrace. SourcePath: {sourcePath} - LineIndex: {lineIndex}"); var sectionBuilder = new StringBuilder(); try { lock (_pdbsLock) { if (_pdbs == null) { _pdbs = new List <PdbFileInfo>(); var currentDirectory = new DirectoryInfo(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)).FullName; _traceLogger.Verbose($"Exploring {currentDirectory} directory for PDB files"); var pdbFilePaths = DirectoryScanner.FindPdbPaths(currentDirectory); foreach (var pdbFilePath in pdbFilePaths) { var pdbFileInfo = new PdbFileInfo(pdbFilePath); try { pdbFileInfo.LoadSourceLinks(); } catch (NotSupportedException exp) { _traceLogger.Warn($"{pdbFilePath} format is not supported. Try to change it to 'portable' or 'embedded'. {Environment.NewLine}{exp}"); } _pdbs.Add(pdbFileInfo); } } } var pdb = _pdbs.FirstOrDefault(p => p.SourceLinks.ContainsKey(sourcePath)); // if defined if (pdb != null) { var content = pdb.GetSourceLinkContent(sourcePath); // if available if (content != null) { var contentLines = content.Replace("\r\n", "\n").Split(new string[] { "\n" }, StringSplitOptions.None); // up var offsetUp = Config.GetValue("Extensions:SourceBack:OffsetUp", 4); var takeFromIndex = lineIndex - offsetUp; var missingTopLinesCount = 0; if (takeFromIndex < 0) { missingTopLinesCount = Math.Abs(takeFromIndex); takeFromIndex = 0; } // down var offsetDown = Config.GetValue("Extensions:SourceBack:OffsetDown", 2); var takeToIndex = lineIndex + offsetDown; if (takeToIndex > contentLines.Length - 1) { takeToIndex = contentLines.Length - 1; } // and add whitespace to replace it with ► var frameContentLines = contentLines.Skip(takeFromIndex + 1).Take(takeToIndex - takeFromIndex).Select(l => " " + l).ToList(); var hightlightFrameLineIndex = offsetUp - missingTopLinesCount - 1; frameContentLines[hightlightFrameLineIndex] = "►" + frameContentLines[hightlightFrameLineIndex].Remove(0, 1); var frameContent = string.Join(Environment.NewLine, frameContentLines); sectionBuilder.AppendLine($"```{Environment.NewLine}{frameContent}{Environment.NewLine}```"); } } } catch (Exception exp) { sectionBuilder.AppendLine($"```{Environment.NewLine}SourceBack error: {exp}{Environment.NewLine}```"); } handled = true; if (!string.IsNullOrEmpty(sectionBuilder.ToString())) { var sourceFileName = Path.GetFileName(sourcePath); var lineWithEditLink = lineWithoutMarkdown.Replace("\\" + sourceFileName, $"\\\\**{sourceFileName}**"); lineWithEditLink = lineWithEditLink.Remove(lineWithEditLink.Length - match.Groups[2].Value.Length); var openWith = Config.GetValue("Extensions:SourceBack:OpenWith", "vscode"); switch (openWith.ToLowerInvariant()) { case "vscode": lineWithEditLink += $"[{match.Groups[2].Value}](vscode://file/{sourcePath.Replace("\\", "/")}:{lineIndex + 1})"; break; } fullMessageBuilder.AppendLine($"{lineWithEditLink}{Environment.NewLine}{sectionBuilder}"); } else { fullMessageBuilder.AppendLine(lineWithoutMarkdown); } } else { fullMessageBuilder.AppendLine(lineWithoutMarkdown); } } } if (handled) { logRequest.Text = fullMessageBuilder.ToString(); } }