Esempio n. 1
0
        /// <summary>
        /// Correlate the current HTTP request according to the previously configured <see cref="CorrelationInfoOptions"/>;
        /// returning an <paramref name="errorMessage"/> when the correlation failed.
        /// </summary>
        /// <param name="errorMessage">The failure message that describes why the correlation of the HTTP request wasn't successful.</param>
        /// <returns>
        ///     <para>[true] when the HTTP request was successfully correlated and the HTTP response was altered accordingly;</para>
        ///     <para>[false] there was a problem with the correlation, describing the failure in the <paramref name="errorMessage"/>.</para>
        /// </returns>
        /// <exception cref="ArgumentNullException">Thrown when the given <see cref="HttpContext"/> is not available to correlate the request with the response.</exception>
        /// <exception cref="ArgumentException">Thrown when the given <see cref="HttpContext"/> doesn't have any response headers to set the correlation headers.</exception>
        public bool TryHttpCorrelate(out string errorMessage)
        {
            HttpContext httpContext = _httpContextAccessor.HttpContext;

            Guard.NotNull(httpContext, nameof(httpContext), "Requires a HTTP context from the HTTP context accessor to start correlating the HTTP request");
            Guard.For <ArgumentException>(() => httpContext.Response is null, "Requires a 'Response'");
            Guard.For <ArgumentException>(() => httpContext.Response.Headers is null, "Requires a 'Response' object with headers");

            if (httpContext.Request.Headers.TryGetValue(_options.Transaction.HeaderName, out StringValues transactionIds))
            {
                if (!_options.Transaction.AllowInRequest)
                {
                    _logger.LogError("No correlation request header '{HeaderName}' for transaction ID was allowed in request", _options.Transaction.HeaderName);

                    errorMessage = $"No correlation transaction ID request header '{_options.Transaction.HeaderName}' was allowed in the request";
                    return(false);
                }

                _logger.LogTrace("Correlation request header '{HeaderName}' found with transaction ID '{TransactionId}'", _options.Transaction.HeaderName, transactionIds);
            }

            string operationId   = DetermineOperationId(httpContext);
            string transactionId = DetermineTransactionId(transactionIds);
            var    correlation   = new CorrelationInfo(operationId, transactionId);

            httpContext.Features.Set(correlation);

            AddCorrelationResponseHeaders(httpContext);

            errorMessage = null;
            return(true);
        }
        /// <summary>Request handling method.</summary>
        /// <param name="httpContext">The <see cref="T:Microsoft.AspNetCore.Http.HttpContext" /> for the current request.</param>
        /// <returns>A <see cref="T:System.Threading.Tasks.Task" /> that represents the execution of this middleware.</returns>
        public async Task Invoke(HttpContext httpContext)
        {
            Guard.NotNull(httpContext, nameof(httpContext));
            Guard.For <ArgumentException>(() => httpContext.Response is null, "Requires a 'Response'");
            Guard.For <ArgumentException>(() => httpContext.Response.Headers is null, "Requires a 'Response' object with headers");

            if (httpContext.Request.Headers.TryGetValue(_options.Transaction.HeaderName, out StringValues transactionIds))
            {
                if (!_options.Transaction.AllowInRequest)
                {
                    _logger.LogError("No correlation request header '{HeaderName}' for transaction ID was allowed in request", _options.Transaction.HeaderName);
                    httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
                    await httpContext.Response.WriteAsync($"No correlation transaction ID request header '{_options.Transaction.HeaderName}' was allowed in the request");

                    return;
                }

                _logger.LogTrace("Correlation request header '{HeaderName}' found with transaction ID '{TransactionId}'", _options.Transaction.HeaderName, transactionIds);
            }

            string operationId   = DetermineOperationId(httpContext);
            string transactionId = DetermineTransactionId(transactionIds);
            var    correlation   = new CorrelationInfo(operationId, transactionId);

            httpContext.Features.Set(correlation);

            AddCorrelationResponseHeaders(httpContext, operationId, transactionId);

            await _next(httpContext);
        }
        public async Task SendRequest_WithSerilogCorrelationenrichment_ReturnsOkWithDifferentOperationIdAndDifferentTransactionId()
        {
            // Arrange
            using (HttpClient client = _testServer.CreateClient())
                // Act
                using (HttpResponseMessage firstResponse = await client.GetAsync(Route))
                {
                    // Assert
                    Assert.Equal(HttpStatusCode.OK, firstResponse.StatusCode);
                    CorrelationInfo firstCorrelationInfo = await AssertAppCorrelationInfoAsync(firstResponse);

                    AssertLoggedCorrelationProperties(firstCorrelationInfo);

                    using (HttpResponseMessage secondResponse = await client.GetAsync(Route))
                    {
                        Assert.Equal(HttpStatusCode.OK, secondResponse.StatusCode);
                        CorrelationInfo secondCorrelationInfo = await AssertAppCorrelationInfoAsync(secondResponse);

                        AssertLoggedCorrelationProperties(secondCorrelationInfo);

                        Assert.NotEqual(firstCorrelationInfo.OperationId, secondCorrelationInfo.OperationId);
                        Assert.NotEqual(firstCorrelationInfo.TransactionId, secondCorrelationInfo.TransactionId);
                    }
                }
        }
        public async Task SendRequestWithCorrelateInfo_SetsCorrelationInfo_ResponseWithUpdatedCorrelationInfo()
        {
            // Arrange
            var correlationInfo = new CorrelationInfo($"operation-{Guid.NewGuid()}", $"transaction-{Guid.NewGuid()}");
            var options         = new TestApiServerOptions()
                                  .ConfigureServices(services => services.AddHttpCorrelation())
                                  .PreConfigure(app => app.UseHttpCorrelation());

            await using (var server = await TestApiServer.StartNewAsync(options, _logger))
            {
                var request = HttpRequestBuilder.Post(CorrelationController.SetCorrelationRoute)
                              .WithHeader(DefaultOperationId, correlationInfo.OperationId)
                              .WithHeader(DefaultTransactionId, correlationInfo.TransactionId);

                // Act
                using (HttpResponseMessage response = await server.SendAsync(request))
                {
                    // Assert
                    Assert.Equal(HttpStatusCode.OK, response.StatusCode);

                    string actualOperationId   = GetResponseHeader(response, DefaultOperationId);
                    string actualTransactionId = GetResponseHeader(response, DefaultTransactionId);

                    Assert.Equal(correlationInfo.OperationId, actualOperationId);
                    Assert.Equal(correlationInfo.TransactionId, actualTransactionId);
                }
            }
        }
Esempio n. 5
0
        public async Task SendRequestWithCorrelateInfo_SetsCorrelationInfo_ResponseWithUpdatedCorrelationInfo()
        {
            // Arrange
            var correlationInfo = new CorrelationInfo($"operation-{Guid.NewGuid()}", $"transaction-{Guid.NewGuid()}");

            using (HttpClient client = _testServer.CreateClient())
                using (var request = new HttpRequestMessage(HttpMethod.Post, SetCorrelationRoute))
                {
                    request.Headers.Add(DefaultOperationId, correlationInfo.OperationId);
                    request.Headers.Add(DefaultTransactionId, correlationInfo.TransactionId);

                    // Act
                    using (HttpResponseMessage response = await client.SendAsync(request))
                    {
                        // Assert
                        Assert.Equal(HttpStatusCode.OK, response.StatusCode);

                        string actualOperationId   = GetResponseHeader(response, DefaultOperationId);
                        string actualTransactionId = GetResponseHeader(response, DefaultTransactionId);

                        Assert.Equal(correlationInfo.OperationId, actualOperationId);
                        Assert.Equal(correlationInfo.TransactionId, actualTransactionId);
                    }
                }
        }
        public async Task SendRequest_WithSerilogCorrelationenrichment_ReturnsOkWithDifferentOperationIdAndSameTransactionId()
        {
            // Arrange
            using (HttpClient client = _testServer.CreateClient())
                // Act
                using (HttpResponseMessage firstResponse = await client.GetAsync(DefaultRoute))
                {
                    // Assert
                    Assert.Equal(HttpStatusCode.OK, firstResponse.StatusCode);
                    CorrelationInfo firstCorrelationInfo = await AssertAppCorrelationInfoAsync(firstResponse);

                    AssertLoggedCorrelationProperties(firstCorrelationInfo);

                    var request = new HttpRequestMessage(HttpMethod.Get, DefaultRoute);
                    request.Headers.Add("X-Transaction-ID", firstCorrelationInfo.TransactionId);

                    using (HttpResponseMessage secondResponse = await client.SendAsync(request))
                    {
                        Assert.Equal(HttpStatusCode.OK, secondResponse.StatusCode);
                        CorrelationInfo secondCorrelationInfo = await AssertAppCorrelationInfoAsync(secondResponse);

                        AssertLoggedCorrelationProperties(secondCorrelationInfo);

                        Assert.NotEqual(firstCorrelationInfo.OperationId, secondCorrelationInfo.OperationId);
                        Assert.Equal(firstCorrelationInfo.TransactionId, secondCorrelationInfo.TransactionId);
                    }
                }
        }
        public async Task SendRequest_WithSerilogCorrelationEnrichment_ReturnsOkWithEnrichedCorrelationLogProperties()
        {
            // Arrange
            var spySink = new InMemorySink();
            var options = new TestApiServerOptions()
                          .ConfigureServices(services => services.AddHttpCorrelation())
                          .PreConfigure(app => app.UseHttpCorrelation())
                          .ConfigureHost(host => host.UseSerilog((context, serviceProvider, config) =>
                                                                 config.Enrich.WithHttpCorrelationInfo(serviceProvider)
                                                                 .WriteTo.Sink(spySink)));

            await using (var server = await TestApiServer.StartNewAsync(options, _logger))
            {
                var request = HttpRequestBuilder.Get(CorrelationController.GetRoute);

                // Act
                using (HttpResponseMessage response = await server.SendAsync(request))
                {
                    // Assert
                    Assert.Equal(HttpStatusCode.OK, response.StatusCode);
                    CorrelationInfo correlationInfo = await AssertAppCorrelationInfoAsync(response);

                    AssertLoggedCorrelationProperties(spySink, correlationInfo);
                }
            }
        }
        private void AssertLoggedCorrelationProperties(CorrelationInfo correlationInfo)
        {
            IEnumerable <KeyValuePair <string, LogEventPropertyValue> > properties =
                _testServer.LogSink.DequeueLogEvents()
                .SelectMany(ev => ev.Properties);

            var transactionIdProperties = properties.Where(prop => prop.Key == TransactionIdPropertyName);
            var operationIdProperties   = properties.Where(prop => prop.Key == OperationIdPropertyName);

            Assert.Contains(transactionIdProperties, prop => correlationInfo.TransactionId == prop.Value.ToStringValue());
            Assert.Contains(operationIdProperties, prop => correlationInfo.OperationId == prop.Value.ToStringValue());
        }
Esempio n. 9
0
        public void Constructor_NoTransactionIdSpecified_Succeeds()
        {
            // Arrange
            var operationId = Guid.NewGuid().ToString();

            // Act
            var messageCorrelationInfo = new CorrelationInfo(operationId, transactionId: null);

            // Assert
            Assert.Equal(operationId, messageCorrelationInfo.OperationId);
            Assert.Null(messageCorrelationInfo.TransactionId);
        }
Esempio n. 10
0
        public void Constructor_Valid_Succeeds()
        {
            // Arrange
            var transactionId = Guid.NewGuid().ToString();
            var operationId   = Guid.NewGuid().ToString();

            // Act
            var messageCorrelationInfo = new CorrelationInfo(operationId, transactionId);

            // Assert
            Assert.Equal(operationId, messageCorrelationInfo.OperationId);
            Assert.Equal(transactionId, messageCorrelationInfo.TransactionId);
        }
        public async Task SendRequest_WithSerilogCorrelationEnrichment_ReturnsOkWithEnrichedCorrelationLogProperties()
        {
            // Arrange
            using (HttpClient client = _testServer.CreateClient())
                // Act
                using (HttpResponseMessage response = await client.GetAsync(Route))
                {
                    // Assert
                    Assert.Equal(HttpStatusCode.OK, response.StatusCode);
                    CorrelationInfo correlationInfo = await AssertAppCorrelationInfoAsync(response);

                    AssertLoggedCorrelationProperties(correlationInfo);
                }
        }
        private static void AssertLoggedCorrelationProperties(InMemorySink testSink, CorrelationInfo correlationInfo)
        {
            KeyValuePair <string, LogEventPropertyValue>[] properties =
                testSink.DequeueLogEvents()
                .SelectMany(ev => ev.Properties)
                .ToArray();

            Assert.Contains(
                properties.Where(prop => prop.Key == TransactionIdPropertyName),
                prop => correlationInfo.TransactionId == prop.Value.ToStringValue());

            Assert.Contains(
                properties.Where(prop => prop.Key == OperationIdPropertyName),
                prop => correlationInfo.OperationId == prop.Value.ToStringValue());
        }
        public async Task SetCorrelationInfo_Twice_UsesMostRecentValue()
        {
            // Arrange
            var firstOperationId  = $"operation-{Guid.NewGuid()}";
            var secondOperationId = $"operation-{Guid.NewGuid()}";
            var transactionId     = $"transaction-{Guid.NewGuid()}";

            await SetCorrelationInfo(firstOperationId, transactionId);

            // Act
            await SetCorrelationInfo(secondOperationId, transactionId);

            // Assert
            CorrelationInfo correlationInfo = DefaultCorrelationInfoAccessor.Instance.GetCorrelationInfo();

            Assert.Equal(secondOperationId, correlationInfo.OperationId);
            Assert.Equal(transactionId, correlationInfo.TransactionId);
        }
Esempio n. 14
0
        public void Correlation_GetCorrelationInfo_UsesStubbedCorrelation()
        {
            // Arrange
            var expected            = new CorrelationInfo($"operation-{Guid.NewGuid()}", $"transaction-{Guid.NewGuid()}");
            var correlationAccessor = new DefaultCorrelationInfoAccessor();

            correlationAccessor.SetCorrelationInfo(expected);

            IOptions <HttpCorrelationInfoOptions> options = Options.Create(new HttpCorrelationInfoOptions());
            IHttpContextAccessor      contextAccessor     = Mock.Of <IHttpContextAccessor>();
            ILogger <HttpCorrelation> logger = NullLogger <HttpCorrelation> .Instance;
            var correlation = new HttpCorrelation(options, contextAccessor, correlationAccessor, logger);

            // Act
            CorrelationInfo actual = correlation.GetCorrelationInfo();

            // Assert
            Assert.Same(expected, actual);
        }
Esempio n. 15
0
        private void AddCorrelationResponseHeaders(HttpContext httpContext)
        {
            if (_options.Operation.IncludeInResponse)
            {
                httpContext.Response.OnStarting(() =>
                {
                    CorrelationInfo correlationInfo = _correlationInfoAccessor.GetCorrelationInfo();
                    AddResponseHeader(httpContext, _options.Operation.HeaderName, correlationInfo.OperationId);
                    return(Task.CompletedTask);
                });
            }

            if (_options.Transaction.IncludeInResponse)
            {
                httpContext.Response.OnStarting(() =>
                {
                    CorrelationInfo correlationInfo = _correlationInfoAccessor.GetCorrelationInfo();
                    AddResponseHeader(httpContext, _options.Transaction.HeaderName, correlationInfo.TransactionId);
                    return(Task.CompletedTask);
                });
            }
        }
        public async Task SendRequest_WithSerilogCorrelationenrichment_ReturnsOkWithDifferentOperationIdAndSameTransactionId()
        {
            // Arrange
            var spySink = new InMemorySink();
            var options = new TestApiServerOptions()
                          .ConfigureServices(services => services.AddHttpCorrelation())
                          .PreConfigure(app => app.UseHttpCorrelation())
                          .ConfigureHost(host => host.UseSerilog((context, serviceProvider, config) =>
                                                                 config.Enrich.WithHttpCorrelationInfo(serviceProvider)
                                                                 .WriteTo.Sink(spySink)));

            await using (var server = await TestApiServer.StartNewAsync(options, _logger))
            {
                var firstRequest = HttpRequestBuilder.Get(CorrelationController.GetRoute);
                using (HttpResponseMessage firstResponse = await server.SendAsync(firstRequest))
                {
                    // Assert
                    Assert.Equal(HttpStatusCode.OK, firstResponse.StatusCode);
                    CorrelationInfo firstCorrelationInfo = await AssertAppCorrelationInfoAsync(firstResponse);

                    AssertLoggedCorrelationProperties(spySink, firstCorrelationInfo);

                    var secondRequest = HttpRequestBuilder
                                        .Get(CorrelationController.GetRoute)
                                        .WithHeader("X-Transaction-ID", firstCorrelationInfo.TransactionId);

                    using (HttpResponseMessage secondResponse = await server.SendAsync(secondRequest))
                    {
                        Assert.Equal(HttpStatusCode.OK, secondResponse.StatusCode);
                        CorrelationInfo secondCorrelationInfo = await AssertAppCorrelationInfoAsync(secondResponse);

                        AssertLoggedCorrelationProperties(spySink, secondCorrelationInfo);

                        Assert.NotEqual(firstCorrelationInfo.OperationId, secondCorrelationInfo.OperationId);
                        Assert.Equal(firstCorrelationInfo.TransactionId, secondCorrelationInfo.TransactionId);
                    }
                }
            }
        }
Esempio n. 17
0
        private void AddCorrelationResponseHeaders(HttpContext httpContext)
        {
            if (_options.Operation.IncludeInResponse)
            {
                _logger.LogTrace("Prepare for the operation correlation ID to be included in the response...");
                httpContext.Response.OnStarting(() =>
                {
                    CorrelationInfo correlationInfo = _correlationInfoAccessor.GetCorrelationInfo();
                    AddResponseHeader(httpContext, _options.Operation.HeaderName, correlationInfo.OperationId);
                    return(Task.CompletedTask);
                });
            }

            if (_options.Transaction.IncludeInResponse)
            {
                _logger.LogTrace("Prepare for the transactional correlation ID to be included in the response...");
                httpContext.Response.OnStarting(() =>
                {
                    CorrelationInfo correlationInfo = _correlationInfoAccessor.GetCorrelationInfo();
                    AddResponseHeader(httpContext, _options.Transaction.HeaderName, correlationInfo.TransactionId);
                    return(Task.CompletedTask);
                });
            }
        }
Esempio n. 18
0
 /// <summary>
 /// Sets the current correlation information for this context.
 /// </summary>
 /// <param name="correlationInfo">The correlation model to set.</param>
 /// <exception cref="ArgumentNullException">Thrown when the <paramref name="correlationInfo"/> is <c>null</c>.</exception>
 public void SetCorrelationInfo(CorrelationInfo correlationInfo)
 {
     Guard.NotNull(correlationInfo, nameof(correlationInfo));
     _correlationInfoAccessor.SetCorrelationInfo(correlationInfo);
 }
Esempio n. 19
0
 /// <summary>
 /// Sets the current correlation information for this context.
 /// </summary>
 /// <param name="correlationInfo">The correlation model to set.</param>
 public void SetCorrelationInfo(CorrelationInfo correlationInfo)
 {
     throw new NotSupportedException(
               $"The correlation information is automatically set during the application middleware '{nameof(CorrelationMiddleware)}' and is not supported to be altered afterwards");
 }
 /// <summary>
 /// Sets the current correlation information for this context.
 /// </summary>
 /// <param name="correlationInfo">The correlation model to set.</param>
 /// <exception cref="ArgumentNullException">Thrown when the <paramref name="correlationInfo"/> is <c>null</c>.</exception>
 public void SetCorrelationInfo(CorrelationInfo correlationInfo)
 {
     Guard.NotNull(correlationInfo, nameof(correlationInfo));
     _httpContextAccessor.HttpContext?.Features?.Set(correlationInfo);
 }
 public void SetCorrelationInfo(CorrelationInfo correlationInfo)
 {
 }