public async Task ResponseHeadersLogs() { var options = CreateOptionsAccessor(); options.CurrentValue.LoggingFields = HttpLoggingFields.ResponseHeaders; var middleware = new HttpLoggingMiddleware( async c => { c.Response.StatusCode = 200; c.Response.Headers[HeaderNames.TransferEncoding] = "test"; c.Response.ContentType = "text/plain"; await c.Response.WriteAsync("test"); }, options, LoggerFactory.CreateLogger <HttpLoggingMiddleware>()); var httpContext = new DefaultHttpContext(); await middleware.Invoke(httpContext); Assert.DoesNotContain(TestSink.Writes, w => w.Message.Contains("StatusCode: 200")); Assert.Contains(TestSink.Writes, w => w.Message.Contains("Transfer-Encoding: test")); Assert.DoesNotContain(TestSink.Writes, w => w.Message.Contains("Body: test")); }
public async Task LogsWrittenOutsideUpgradeWrapperIfUpgradeDoesNotOccur(bool isUpgradableRequest) { var options = CreateOptionsAccessor(); options.CurrentValue.LoggingFields = HttpLoggingFields.ResponsePropertiesAndHeaders; var httpContext = new DefaultHttpContext(); var upgradeFeatureMock = new Mock <IHttpUpgradeFeature>(); upgradeFeatureMock.Setup(m => m.IsUpgradableRequest).Returns(isUpgradableRequest); httpContext.Features.Set <IHttpUpgradeFeature>(upgradeFeatureMock.Object); var middleware = new HttpLoggingMiddleware( async c => { c.Response.StatusCode = 200; c.Response.Headers[HeaderNames.TransferEncoding] = "test"; c.Response.ContentType = "text/plain"; await c.Response.StartAsync(); }, options, LoggerFactory.CreateLogger <HttpLoggingMiddleware>()); var middlewareTask = middleware.Invoke(httpContext); Assert.Contains(TestSink.Writes, w => w.Message.Contains("StatusCode: 200")); Assert.Contains(TestSink.Writes, w => w.Message.Contains("Transfer-Encoding: test")); Assert.Contains(TestSink.Writes, w => w.Message.Contains("Content-Type: text/plain")); Assert.Null( Record.Exception(() => upgradeFeatureMock.Verify(m => m.UpgradeAsync(), Times.Never))); await middlewareTask; }
public static async Task Sends_notification_when_exception_is_thrown() { var mockRequestDelegate = new Mock <RequestDelegate>(); mockRequestDelegate .Setup(d => d.Invoke(It.IsAny <HttpContext>())) .Throws(new Exception("Something went wrong")); var mockNotificationRepository = new Mock <INotificationRepository>(); var middleware = new HttpLoggingMiddleware( Mock.Of <ILogger <HttpLoggingMiddleware> >(), mockRequestDelegate.Object, Mock.Of <IDiagnosticContext>()); try { await middleware.Invoke(Mock.Of <HttpContext>(), mockNotificationRepository.Object); } // ReSharper disable once EmptyGeneralCatchClause catch { } mockNotificationRepository.Verify( r => r.Send( "Unhandled exception", It.Is <string>(s => s.StartsWith("System.Exception: Something went wrong"))), Times.Once); }
public async Task VerifyDefaultMediaTypeHeaders(string contentType) { // media headers that should work. var expected = new string('a', 1000); var options = CreateOptionsAccessor(); options.CurrentValue.LoggingFields = HttpLoggingFields.RequestBody; var middleware = new HttpLoggingMiddleware( async c => { var arr = new byte[4096]; while (true) { var res = await c.Request.Body.ReadAsync(arr); if (res == 0) { break; } } }, options, LoggerFactory.CreateLogger <HttpLoggingMiddleware>()); var httpContext = new DefaultHttpContext(); httpContext.Request.ContentType = contentType; httpContext.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes(expected)); await middleware.Invoke(httpContext); Assert.Contains(TestSink.Writes, w => w.Message.Contains(expected)); }
public async Task NoopWhenLoggingDisabled() { var options = CreateOptionsAccessor(); options.CurrentValue.LoggingFields = HttpLoggingFields.None; var middleware = new HttpLoggingMiddleware( c => { c.Response.StatusCode = 200; return(Task.CompletedTask); }, options, LoggerFactory.CreateLogger <HttpLoggingMiddleware>()); var httpContext = new DefaultHttpContext(); httpContext.Request.Protocol = "HTTP/1.0"; httpContext.Request.Method = "GET"; httpContext.Request.Scheme = "http"; httpContext.Request.Path = new PathString("/foo"); httpContext.Request.PathBase = new PathString("/foo"); httpContext.Request.QueryString = new QueryString("?foo"); httpContext.Request.Headers["Connection"] = "keep-alive"; httpContext.Request.ContentType = "text/plain"; httpContext.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("test")); await middleware.Invoke(httpContext); Assert.Empty(TestSink.Writes); }
public async Task RequestBodyReadingWorks(string expected) { var options = CreateOptionsAccessor(); options.CurrentValue.LoggingFields = HttpLoggingFields.RequestBody; var middleware = new HttpLoggingMiddleware( async c => { var arr = new byte[4096]; while (true) { var res = await c.Request.Body.ReadAsync(arr); if (res == 0) { break; } } }, options, LoggerFactory.CreateLogger <HttpLoggingMiddleware>()); var httpContext = new DefaultHttpContext(); httpContext.Request.ContentType = "text/plain"; httpContext.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes(expected)); await middleware.Invoke(httpContext); Assert.Contains(TestSink.Writes, w => w.Message.Contains(expected)); }
public async Task PartialReadBodyStillLogs() { var input = string.Concat(new string('a', 60000), new string('b', 3000)); var options = CreateOptionsAccessor(); options.CurrentValue.LoggingFields = HttpLoggingFields.RequestBody; var middleware = new HttpLoggingMiddleware( async c => { var arr = new byte[4096]; var res = await c.Request.Body.ReadAsync(arr); }, options, LoggerFactory.CreateLogger <HttpLoggingMiddleware>()); var httpContext = new DefaultHttpContext(); httpContext.Request.ContentType = "text/plain"; httpContext.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes(input)); await middleware.Invoke(httpContext); var expected = input.Substring(0, 4096); Assert.Contains(TestSink.Writes, w => w.Message.Equals("RequestBody: " + expected)); }
public async Task UnknownRequestHeadersRedacted() { var middleware = new HttpLoggingMiddleware( async c => { var arr = new byte[4096]; while (true) { var res = await c.Request.Body.ReadAsync(arr); if (res == 0) { break; } } }, CreateOptionsAccessor(), LoggerFactory.CreateLogger <HttpLoggingMiddleware>()); var httpContext = new DefaultHttpContext(); httpContext.Request.Headers["foo"] = "bar"; await middleware.Invoke(httpContext); Assert.Contains(TestSink.Writes, w => w.Message.Contains("foo: [Redacted]")); Assert.DoesNotContain(TestSink.Writes, w => w.Message.Contains("foo: bar")); }
public async Task CanConfigureRequestAllowList() { var options = CreateOptionsAccessor(); options.CurrentValue.RequestHeaders.Clear(); options.CurrentValue.RequestHeaders.Add("foo"); var middleware = new HttpLoggingMiddleware( c => { return(Task.CompletedTask); }, options, LoggerFactory.CreateLogger <HttpLoggingMiddleware>()); var httpContext = new DefaultHttpContext(); // Header on the default allow list. httpContext.Request.Headers["Connection"] = "keep-alive"; httpContext.Request.Headers["foo"] = "bar"; await middleware.Invoke(httpContext); Assert.Contains(TestSink.Writes, w => w.Message.Contains("foo: bar")); Assert.DoesNotContain(TestSink.Writes, w => w.Message.Contains("foo: [Redacted]")); Assert.Contains(TestSink.Writes, w => w.Message.Contains("Connection: [Redacted]")); Assert.DoesNotContain(TestSink.Writes, w => w.Message.Contains("Connection: keep-alive")); }
public static async Task Logs_incoming_request_data_when_exception_is_thrown() { var mockDiagnosticContext = new Mock <IDiagnosticContext>(); var mockRequestDelegate = new Mock <RequestDelegate>(); mockRequestDelegate .Setup(d => d.Invoke(It.IsAny <HttpContext>())) .Throws(new Exception("Something went wrong")); var context = new DefaultHttpContext { Request = { Body = new MemoryStream(Encoding.UTF8.GetBytes("__BODY_CONTENT__")) } }; var middleware = new HttpLoggingMiddleware( Mock.Of <ILogger <HttpLoggingMiddleware> >(), mockRequestDelegate.Object, mockDiagnosticContext.Object); try { await middleware.Invoke(context, Mock.Of <INotificationRepository>()); } // ReSharper disable once EmptyGeneralCatchClause catch { } mockDiagnosticContext.Verify(c => c.Set("RequestBody", "__BODY_CONTENT__", false)); }
public static async Task Logs_incoming_request_data() { var mockDiagnosticContext = new Mock <IDiagnosticContext>(); var middleware = new HttpLoggingMiddleware( Mock.Of <ILogger <HttpLoggingMiddleware> >(), Mock.Of <RequestDelegate>(), mockDiagnosticContext.Object); var context = new DefaultHttpContext { Connection = { RemoteIpAddress = new IPAddress(0x2414188f) }, Request = { Method = "PATCH", Path = "/requests?userId=user1", Body = new MemoryStream(Encoding.UTF8.GetBytes("__BODY_CONTENT__")) }, User = new ClaimsPrincipal( new ClaimsIdentity(new[] { new Claim("type1", "value1"), new Claim("type2", "value2") })), Response = { StatusCode = 200 }, }; context.Request.Headers.Add("key1", new StringValues(new[] { "value1a", "value1b" })); context.Request.Headers.Add("key2", new StringValues("value2")); await middleware.Invoke(context, Mock.Of <INotificationRepository>()); var expectedHeaders = new Dictionary <string, string> { { "key1", "value1a; value1b" }, { "key2", "value2" } }; var expectedClaims = new Dictionary <string, string> { { "type1", "value1" }, { "type2", "value2" } }; mockDiagnosticContext.Verify(c => c.Set("RemoteIpAddress", new IPAddress(0x2414188f), false)); mockDiagnosticContext.Verify( c => c.Set( "UserClaims", It.Is <IEnumerable <KeyValuePair <string, string> > >( actual => CheckDictionary(new Dictionary <string, string>(actual), expectedClaims)), false)); mockDiagnosticContext.Verify( c => c.Set( "RequestHeaders", It.Is <IEnumerable <KeyValuePair <string, string> > >( actual => CheckDictionary(new Dictionary <string, string>(actual), expectedHeaders)), false)); mockDiagnosticContext.Verify(c => c.Set("RequestBody", "__BODY_CONTENT__", false)); mockDiagnosticContext.Verify(c => c.Set("StatusCode", 200, false)); }
public async Task AllowedResponseHeadersModify() { var options = CreateOptionsAccessor(); options.CurrentValue.LoggingFields = HttpLoggingFields.ResponseHeaders; options.CurrentValue.ResponseHeaders.Clear(); options.CurrentValue.ResponseHeaders.Add("Test"); var middleware = new HttpLoggingMiddleware( c => { c.Response.Headers["Test"] = "Kestrel"; c.Response.Headers["Server"] = "Kestrel"; return(Task.CompletedTask); }, options, LoggerFactory.CreateLogger <HttpLoggingMiddleware>()); var httpContext = new DefaultHttpContext(); await middleware.Invoke(httpContext); Assert.Contains(TestSink.Writes, w => w.Message.Contains("Test: Kestrel")); Assert.Contains(TestSink.Writes, w => w.Message.Contains("Server: [Redacted]")); }
public static async Task Throws_original_exception() { var mockRequestDelegate = new Mock <RequestDelegate>(); mockRequestDelegate .Setup(d => d.Invoke(It.IsAny <HttpContext>())) .Throws(new Exception("Something went wrong")); var middleware = new HttpLoggingMiddleware( Mock.Of <ILogger <HttpLoggingMiddleware> >(), mockRequestDelegate.Object, Mock.Of <IDiagnosticContext>()); await Assert.ThrowsAsync <Exception>(async() => await middleware.Invoke(new DefaultHttpContext(), Mock.Of <INotificationRepository>())); }
public async Task UpgradeToWebSocketDoesNotLogWhenResponseIsFlushedIfLoggingOptionsAreOtherThanResponseStatusCodeOrResponseHeaders() { var options = CreateOptionsAccessor(); options.CurrentValue.LoggingFields = HttpLoggingFields.All ^ HttpLoggingFields.ResponsePropertiesAndHeaders; var writtenHeaders = new TaskCompletionSource(); var letBodyFinish = new TaskCompletionSource(); var httpContext = new DefaultHttpContext(); var upgradeFeatureMock = new Mock <IHttpUpgradeFeature>(); upgradeFeatureMock.Setup(m => m.IsUpgradableRequest).Returns(true); upgradeFeatureMock .Setup(m => m.UpgradeAsync()) .Callback(() => { httpContext.Response.StatusCode = StatusCodes.Status101SwitchingProtocols; httpContext.Response.Headers[HeaderNames.Connection] = HeaderNames.Upgrade; }) .ReturnsAsync(Stream.Null); httpContext.Features.Set <IHttpUpgradeFeature>(upgradeFeatureMock.Object); var middleware = new HttpLoggingMiddleware( async c => { await c.Features.Get <IHttpUpgradeFeature>().UpgradeAsync(); writtenHeaders.SetResult(); await letBodyFinish.Task; }, options, LoggerFactory.CreateLogger <HttpLoggingMiddleware>()); var middlewareTask = middleware.Invoke(httpContext); await writtenHeaders.Task; Assert.DoesNotContain(TestSink.Writes, w => w.Message.Contains("StatusCode: 101")); Assert.DoesNotContain(TestSink.Writes, w => w.Message.Contains("Connection: Upgrade")); letBodyFinish.SetResult(); await middlewareTask; }
public async Task RequestHeadersLogs() { var options = CreateOptionsAccessor(); options.CurrentValue.LoggingFields = HttpLoggingFields.RequestHeaders; var middleware = new HttpLoggingMiddleware( async c => { var arr = new byte[4096]; while (true) { var res = await c.Request.Body.ReadAsync(arr); if (res == 0) { break; } } }, options, LoggerFactory.CreateLogger <HttpLoggingMiddleware>()); var httpContext = new DefaultHttpContext(); httpContext.Request.Protocol = "HTTP/1.0"; httpContext.Request.Method = "GET"; httpContext.Request.Scheme = "http"; httpContext.Request.Path = new PathString("/foo"); httpContext.Request.PathBase = new PathString("/foo"); httpContext.Request.QueryString = new QueryString("?foo"); httpContext.Request.Headers["Connection"] = "keep-alive"; httpContext.Request.ContentType = "text/plain"; httpContext.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("test")); await middleware.Invoke(httpContext); Assert.DoesNotContain(TestSink.Writes, w => w.Message.Contains("Protocol: HTTP/1.0")); Assert.DoesNotContain(TestSink.Writes, w => w.Message.Contains("Method: GET")); Assert.DoesNotContain(TestSink.Writes, w => w.Message.Contains("Scheme: http")); Assert.DoesNotContain(TestSink.Writes, w => w.Message.Contains("Path: /foo")); Assert.DoesNotContain(TestSink.Writes, w => w.Message.Contains("PathBase: /foo")); Assert.DoesNotContain(TestSink.Writes, w => w.Message.Contains("QueryString: ?foo")); Assert.Contains(TestSink.Writes, w => w.Message.Contains("Connection: keep-alive")); Assert.DoesNotContain(TestSink.Writes, w => w.Message.Contains("Body: test")); }
public async Task ResponseBodyWritingWorks(string expected) { var options = CreateOptionsAccessor(); options.CurrentValue.LoggingFields = HttpLoggingFields.ResponseBody; var middleware = new HttpLoggingMiddleware( c => { c.Response.ContentType = "text/plain"; return(c.Response.WriteAsync(expected)); }, options, LoggerFactory.CreateLogger <HttpLoggingMiddleware>()); var httpContext = new DefaultHttpContext(); await middleware.Invoke(httpContext); Assert.Contains(TestSink.Writes, w => w.Message.Contains(expected)); }
public async Task UnrecognizedMediaType() { var expected = "Hello world"; var options = CreateOptionsAccessor(); options.CurrentValue.LoggingFields = HttpLoggingFields.ResponseBody; var middleware = new HttpLoggingMiddleware( c => { c.Response.ContentType = "foo/*"; return(c.Response.WriteAsync(expected)); }, options, LoggerFactory.CreateLogger <HttpLoggingMiddleware>()); var httpContext = new DefaultHttpContext(); await middleware.Invoke(httpContext); Assert.Contains(TestSink.Writes, w => w.Message.Contains("Unrecognized Content-Type for response body.")); }
public static async Task Sends_notification_on_HTTP_error() { var mockNotificationRepository = new Mock <INotificationRepository>(); var middleware = new HttpLoggingMiddleware( Mock.Of <ILogger <HttpLoggingMiddleware> >(), Mock.Of <RequestDelegate>(), Mock.Of <IDiagnosticContext>()); var context = new DefaultHttpContext { Request = { Method = "GET", Path = "/overview" }, Response = { StatusCode = 400 }, }; await middleware.Invoke(context, mockNotificationRepository.Object); mockNotificationRepository.Verify( r => r.Send("HTTP 400 error", It.Is <string>(v => v.Contains("GET") && v.Contains("/overview"))), Times.Once); }
public async Task DefaultResponseInfoOnlyHeadersAndRequestInfo() { var middleware = new HttpLoggingMiddleware( async c => { c.Response.StatusCode = 200; c.Response.Headers[HeaderNames.TransferEncoding] = "test"; c.Response.ContentType = "text/plain"; await c.Response.WriteAsync("test"); }, CreateOptionsAccessor(), LoggerFactory.CreateLogger <HttpLoggingMiddleware>()); var httpContext = new DefaultHttpContext(); await middleware.Invoke(httpContext); Assert.Contains(TestSink.Writes, w => w.Message.Contains("StatusCode: 200")); Assert.Contains(TestSink.Writes, w => w.Message.Contains("Transfer-Encoding: test")); Assert.DoesNotContain(TestSink.Writes, w => w.Message.Contains("Body: test")); }
public async Task DifferentEncodingsWork() { var encoding = Encoding.Unicode; var expected = new string('a', 1000); var options = CreateOptionsAccessor(); options.CurrentValue.LoggingFields = HttpLoggingFields.RequestBody; options.CurrentValue.MediaTypeOptions.Clear(); options.CurrentValue.MediaTypeOptions.AddText("text/plain", encoding); var middleware = new HttpLoggingMiddleware( async c => { var arr = new byte[4096]; var count = 0; while (true) { var res = await c.Request.Body.ReadAsync(arr); if (res == 0) { break; } count += res; } Assert.Equal(2000, count); }, options, LoggerFactory.CreateLogger <HttpLoggingMiddleware>()); var httpContext = new DefaultHttpContext(); httpContext.Request.ContentType = "text/plain"; httpContext.Request.Body = new MemoryStream(encoding.GetBytes(expected)); await middleware.Invoke(httpContext); Assert.Contains(TestSink.Writes, w => w.Message.Contains(expected)); }
public async Task RequestBodyReadingLimitLongCharactersWorks() { var input = string.Concat(new string('あ', 5)); var options = CreateOptionsAccessor(); options.CurrentValue.LoggingFields = HttpLoggingFields.RequestBody; options.CurrentValue.RequestBodyLogLimit = 4; var middleware = new HttpLoggingMiddleware( async c => { var arr = new byte[4096]; var count = 0; while (true) { var res = await c.Request.Body.ReadAsync(arr); if (res == 0) { break; } count += res; } Assert.Equal(15, count); }, options, LoggerFactory.CreateLogger <HttpLoggingMiddleware>()); var httpContext = new DefaultHttpContext(); httpContext.Request.ContentType = "text/plain"; httpContext.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes(input)); await middleware.Invoke(httpContext); var expected = input.Substring(0, options.CurrentValue.RequestBodyLogLimit / 3); Assert.Contains(TestSink.Writes, w => w.Message.Equals("RequestBody: " + expected)); }
public async Task FirstWriteResponseHeadersLogged() { var options = CreateOptionsAccessor(); options.CurrentValue.LoggingFields = HttpLoggingFields.Response; var writtenHeaders = new TaskCompletionSource(); var letBodyFinish = new TaskCompletionSource(); var middleware = new HttpLoggingMiddleware( async c => { c.Response.StatusCode = 200; c.Response.Headers[HeaderNames.TransferEncoding] = "test"; c.Response.ContentType = "text/plain"; await c.Response.WriteAsync("test"); writtenHeaders.SetResult(); await letBodyFinish.Task; }, options, LoggerFactory.CreateLogger <HttpLoggingMiddleware>()); var httpContext = new DefaultHttpContext(); var middlewareTask = middleware.Invoke(httpContext); await writtenHeaders.Task; Assert.Contains(TestSink.Writes, w => w.Message.Contains("StatusCode: 200")); Assert.Contains(TestSink.Writes, w => w.Message.Contains("Transfer-Encoding: test")); Assert.DoesNotContain(TestSink.Writes, w => w.Message.Contains("Body: test")); letBodyFinish.SetResult(); await middlewareTask; Assert.Contains(TestSink.Writes, w => w.Message.Contains("Body: test")); }
public async Task ResponseBodyWritingLimitWorks() { var input = string.Concat(new string('a', 30000), new string('b', 3000)); var options = CreateOptionsAccessor(); options.CurrentValue.LoggingFields = HttpLoggingFields.ResponseBody; var middleware = new HttpLoggingMiddleware( c => { c.Response.ContentType = "text/plain"; return(c.Response.WriteAsync(input)); }, options, LoggerFactory.CreateLogger <HttpLoggingMiddleware>()); var httpContext = new DefaultHttpContext(); await middleware.Invoke(httpContext); var expected = input.Substring(0, options.CurrentValue.ResponseBodyLogLimit); Assert.Contains(TestSink.Writes, w => w.Message.Contains(expected)); }
public async Task UpgradeToWebSocketLogsWrittenOnlyOnce(HttpLoggingFields loggingFields) { var options = CreateOptionsAccessor(); options.CurrentValue.LoggingFields = loggingFields; var httpContext = new DefaultHttpContext(); var upgradeFeatureMock = new Mock <IHttpUpgradeFeature>(); upgradeFeatureMock.Setup(m => m.IsUpgradableRequest).Returns(true); upgradeFeatureMock .Setup(m => m.UpgradeAsync()) .Callback(() => { httpContext.Response.StatusCode = StatusCodes.Status101SwitchingProtocols; httpContext.Response.Headers[HeaderNames.Connection] = HeaderNames.Upgrade; }) .ReturnsAsync(Stream.Null); httpContext.Features.Set <IHttpUpgradeFeature>(upgradeFeatureMock.Object); var writeCount = 0; TestSink.MessageLogged += (context) => { writeCount++; }; var middleware = new HttpLoggingMiddleware( async c => { await c.Features.Get <IHttpUpgradeFeature>().UpgradeAsync(); }, options, LoggerFactory.CreateLogger <HttpLoggingMiddleware>()); await middleware.Invoke(httpContext); Assert.Equal(1, writeCount); }
public async Task OriginalUpgradeFeatureIsRestoredBeforeMiddlewareCompletes(HttpLoggingFields loggingFields) { var options = CreateOptionsAccessor(); options.CurrentValue.LoggingFields = loggingFields; var letBodyFinish = new TaskCompletionSource(); var httpContext = new DefaultHttpContext(); var upgradeFeatureMock = new Mock <IHttpUpgradeFeature>(); upgradeFeatureMock.Setup(m => m.IsUpgradableRequest).Returns(true); upgradeFeatureMock.Setup(m => m.UpgradeAsync()).ReturnsAsync(Stream.Null); httpContext.Features.Set <IHttpUpgradeFeature>(upgradeFeatureMock.Object); IHttpUpgradeFeature upgradeFeature = null; var middleware = new HttpLoggingMiddleware( async c => { upgradeFeature = c.Features.Get <IHttpUpgradeFeature>(); await letBodyFinish.Task; }, options, LoggerFactory.CreateLogger <HttpLoggingMiddleware>()); var middlewareTask = middleware.Invoke(httpContext); Assert.True(upgradeFeature is UpgradeFeatureLoggingDecorator); letBodyFinish.SetResult(); await middlewareTask; Assert.False(httpContext.Features.Get <IHttpUpgradeFeature>() is UpgradeFeatureLoggingDecorator); }