/// <summary> /// Tests if the contents of the given url can be accessed from the current environment. /// Reports results to given data service. /// </summary> /// <param name="log"><see cref="ILogger"/> that is used to report log information.</param> /// <param name="monitorName">Name of this monitor to be included in the logs and in the data sent to Kusto.</param> /// <param name="url">Url that this method will attempt to access.</param> /// <param name="dataService">Data service to be used when reporting the results.</param> /// <param name="cancellationToken">Token to cancel the asynchronous operation.</param> /// <returns>A task, tracking the initiated async operation. Errors should be reported through exceptions.</returns> internal static async Task CheckAndReportUrlAccessAsync(ILogger log, string monitorName, string url, IDataService dataService, CancellationToken cancellationToken = default) { _ = log ?? throw new ArgumentNullException(paramName: nameof(log)); _ = string.IsNullOrWhiteSpace(monitorName) ? throw new ArgumentNullException(paramName: nameof(monitorName)) : monitorName; _ = string.IsNullOrWhiteSpace(url) ? throw new ArgumentNullException(paramName: nameof(url)) : url; _ = dataService ?? throw new ArgumentNullException(paramName: nameof(dataService)); HttpRequestLogEntry logEntry = new HttpRequestLogEntry() { MonitorName = monitorName, EventTime = DateTime.UtcNow, RequestedUrl = url }; HttpResponseMessage?response = null; try { response = await _httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); logEntry.HttpResponseCode = (int)response.StatusCode; await dataService.ReportUrlAccessAsync(logEntry, cancellationToken).ConfigureAwait(false); } catch (Exception httpException) { if (response == null) { // HttpClient failed to return a response, instead threw. The error should be in the exception. logEntry.Error = httpException.Message; try { await dataService.ReportUrlAccessAsync(logEntry, cancellationToken); } catch (Exception dataServiceException) { // Reporting to database has failed. Let's report into Azure Functions so that we can somehow track this. log.LogError(httpException, $"Failed to access url {url} from monitor {monitorName}"); // There was an error with the data store and this should also be logged. log.LogError(dataServiceException, "Failed to report an unsuccessful http request."); } } else { // Http request completed, but reporting to data service has failed. log.LogError($"Failed to report an http response code {response.StatusCode} for url {url}."); } } finally { response?.Dispose(); } }
/// <summary> /// Reports the details of the <see cref="HttpResponseMessage"/> to kusto. /// </summary> /// <param name="monitorName">Name of the monitor generating this data entry.</param> /// <param name="httpResponse">Response to be reported.</param> /// <returns>A task, tracking this async operation.</returns> public async Task ReportUrlAccessAsync(string monitorName, HttpResponseMessage httpResponse, CancellationToken cancellationToken = default) { HttpRequestLogEntry logEntry = new HttpRequestLogEntry() { MonitorName = monitorName, EventTime = DateTime.UtcNow, RequestedUrl = httpResponse.RequestMessage.RequestUri.AbsoluteUri, HttpResponseCode = (int)httpResponse.StatusCode }; await _httpRequestLogsTable.InsertRowAsync(logEntry, cancellationToken).ConfigureAwait(false); }
/// <summary> /// Saves the details of the <see cref="HttpResponseMessage"/> to /// </summary> /// <param name="monitorName"></param> /// <param name="httpResponse"></param> /// <returns></returns> public async Task ReportUrlAccessAsync(string monitorName, HttpResponseMessage httpResponse, CancellationToken cancellationToken = default) { HttpRequestLogEntry logEntry = new HttpRequestLogEntry() { MonitorName = monitorName, EventTime = DateTime.UtcNow, RequestedUrl = httpResponse.RequestMessage.RequestUri.AbsoluteUri, HttpStatusCode = (int)httpResponse.StatusCode }; KustoConnectionStringBuilder kcsb = new KustoConnectionStringBuilder($"https://ingest-{ServiceNameAndRegion}.kusto.windows.net") .WithAadManagedIdentity("system"); using IKustoQueuedIngestClient ingestClient = KustoIngestFactory.CreateQueuedIngestClient(kcsb); KustoQueuedIngestionProperties ingestProps = new KustoQueuedIngestionProperties(DatabaseName, TableName) { ReportLevel = IngestionReportLevel.FailuresOnly, ReportMethod = IngestionReportMethod.Queue, IngestionMapping = new IngestionMapping() { IngestionMappingKind = Kusto.Data.Ingestion.IngestionMappingKind.Json, IngestionMappings = HttpRequestLogColumnMapping }, Format = DataSourceFormat.json }; using MemoryStream memStream = new MemoryStream(); using StreamWriter writer = new StreamWriter(memStream); writer.WriteLine(JsonConvert.SerializeObject(logEntry)); writer.Flush(); memStream.Seek(0, SeekOrigin.Begin); // IKustoQueuedIngestClient doesn't support cancellation at the moment. Update the line below if it does in the future. await ingestClient.IngestFromStreamAsync(memStream, ingestProps, leaveOpen : true); }
/// <summary> /// Stores the details of the <see cref="HttpRequestLogEntry"/> in the underlying kusto database. /// </summary> /// <inheritdoc/> public async Task ReportUrlAccessAsync(HttpRequestLogEntry httpRequestLogEntry, CancellationToken cancellationToken = default) { await _httpRequestLogsTable.InsertRowAsync(httpRequestLogEntry, cancellationToken).ConfigureAwait(false); }
/// <inheritdoc/> public async Task ReportUrlAccessAsync(HttpRequestLogEntry httpRequestLogEntry, CancellationToken cancellationToken = default) { await Task.Delay(new Random().Next(200, 4000), cancellationToken).ConfigureAwait(false); }