From 1c40548928692d448746ec9b4db09d51b9f57eab Mon Sep 17 00:00:00 2001 From: Chris R Date: Mon, 16 Nov 2015 15:12:12 -0800 Subject: [PATCH] Add LoggingConnectionFilter. --- samples/SampleApp/Startup.cs | 4 +- .../HttpsApplicationBuilderExtensions.cs | 4 +- .../Filter/LoggingConnectionFilter.cs | 37 +++++ ...ggingFilterApplicationBuilderExtensions.cs | 39 ++++++ .../Filter/LoggingStream.cs | 129 ++++++++++++++++++ 5 files changed, 210 insertions(+), 3 deletions(-) create mode 100644 src/Microsoft.AspNet.Server.Kestrel/Filter/LoggingConnectionFilter.cs create mode 100644 src/Microsoft.AspNet.Server.Kestrel/Filter/LoggingFilterApplicationBuilderExtensions.cs create mode 100644 src/Microsoft.AspNet.Server.Kestrel/Filter/LoggingStream.cs diff --git a/samples/SampleApp/Startup.cs b/samples/SampleApp/Startup.cs index 7ececbb741..97cca8e51e 100644 --- a/samples/SampleApp/Startup.cs +++ b/samples/SampleApp/Startup.cs @@ -8,7 +8,7 @@ using Microsoft.AspNet.Builder; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Features; using Microsoft.AspNet.Server.Kestrel; -using Microsoft.AspNet.Server.Kestrel.Https; +using Microsoft.AspNet.Server.Kestrel.Filter; using Microsoft.Extensions.Logging; using Microsoft.Extensions.PlatformAbstractions; @@ -39,6 +39,8 @@ namespace SampleApp Console.WriteLine("Could not find certificate at '{0}'. HTTPS is not enabled.", testCertPath); } + app.UseKestrelConnectionLogging(); + app.Run(async context => { Console.WriteLine("{0} {1}{2}{3}", diff --git a/src/Microsoft.AspNet.Server.Kestrel.Https/HttpsApplicationBuilderExtensions.cs b/src/Microsoft.AspNet.Server.Kestrel.Https/HttpsApplicationBuilderExtensions.cs index c595376aa9..e31f0f6482 100644 --- a/src/Microsoft.AspNet.Server.Kestrel.Https/HttpsApplicationBuilderExtensions.cs +++ b/src/Microsoft.AspNet.Server.Kestrel.Https/HttpsApplicationBuilderExtensions.cs @@ -4,9 +4,9 @@ using System.Security.Cryptography.X509Certificates; using Microsoft.AspNet.Builder; using Microsoft.AspNet.Http.Features; -using Microsoft.AspNet.Server.Kestrel.Filter; +using Microsoft.AspNet.Server.Kestrel.Https; -namespace Microsoft.AspNet.Server.Kestrel.Https +namespace Microsoft.AspNet.Server.Kestrel.Filter { public static class HttpsApplicationBuilderExtensions { diff --git a/src/Microsoft.AspNet.Server.Kestrel/Filter/LoggingConnectionFilter.cs b/src/Microsoft.AspNet.Server.Kestrel/Filter/LoggingConnectionFilter.cs new file mode 100644 index 0000000000..1858715ea9 --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Filter/LoggingConnectionFilter.cs @@ -0,0 +1,37 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNet.Server.Kestrel.Filter +{ + public class LoggingConnectionFilter : IConnectionFilter + { + private readonly ILogger _logger; + private readonly IConnectionFilter _previous; + + public LoggingConnectionFilter(ILogger logger, IConnectionFilter previous) + { + if (logger == null) + { + throw new ArgumentNullException(nameof(logger)); + } + if (previous == null) + { + throw new ArgumentNullException(nameof(previous)); + } + + _logger = logger; + _previous = previous; + } + + public async Task OnConnection(ConnectionFilterContext context) + { + await _previous.OnConnection(context); + + context.Connection = new LoggingStream(context.Connection, _logger); + } + } +} diff --git a/src/Microsoft.AspNet.Server.Kestrel/Filter/LoggingFilterApplicationBuilderExtensions.cs b/src/Microsoft.AspNet.Server.Kestrel/Filter/LoggingFilterApplicationBuilderExtensions.cs new file mode 100644 index 0000000000..de0ffab1ac --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Filter/LoggingFilterApplicationBuilderExtensions.cs @@ -0,0 +1,39 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Http.Features; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNet.Server.Kestrel.Filter +{ + public static class LoggingFilterApplicationBuilderExtensions + { + /// + /// Emits verbose logs for bytes read from and written to the connection. + /// + /// + public static IApplicationBuilder UseKestrelConnectionLogging(this IApplicationBuilder app) + { + return app.UseKestrelConnectionLogging(nameof(LoggingConnectionFilter)); + } + + /// + /// Emits verbose logs for bytes read from and written to the connection. + /// + /// + public static IApplicationBuilder UseKestrelConnectionLogging(this IApplicationBuilder app, string loggerName) + { + var serverInfo = app.ServerFeatures.Get(); + if (serverInfo != null) + { + var prevFilter = serverInfo.ConnectionFilter ?? new NoOpConnectionFilter(); + var loggerFactory = app.ApplicationServices.GetRequiredService(); + var logger = loggerFactory.CreateLogger(loggerName ?? nameof(LoggingConnectionFilter)); + serverInfo.ConnectionFilter = new LoggingConnectionFilter(logger, prevFilter); + } + return app; + } + } +} diff --git a/src/Microsoft.AspNet.Server.Kestrel/Filter/LoggingStream.cs b/src/Microsoft.AspNet.Server.Kestrel/Filter/LoggingStream.cs new file mode 100644 index 0000000000..41787c26ca --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Filter/LoggingStream.cs @@ -0,0 +1,129 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNet.Server.Kestrel.Filter +{ + internal class LoggingStream : Stream + { + private readonly Stream _inner; + private readonly ILogger _logger; + + public LoggingStream(Stream inner, ILogger logger) + { + _inner = inner; + _logger = logger; + } + + public override bool CanRead + { + get + { + return _inner.CanRead; + } + } + + public override bool CanSeek + { + get + { + return _inner.CanSeek; + } + } + + public override bool CanWrite + { + get + { + return _inner.CanWrite; + } + } + + public override long Length + { + get + { + return _inner.Length; + } + } + + public override long Position + { + get + { + return _inner.Position; + } + + set + { + _inner.Position = value; + } + } + + public override void Flush() + { + _inner.Flush(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + int read = _inner.Read(buffer, offset, count); + Log("Read", read, buffer, offset); + return read; + } + + public async override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + int read = await _inner.ReadAsync(buffer, offset, count, cancellationToken); + Log("ReadAsync", read, buffer, offset); + return read; + } + + public override long Seek(long offset, SeekOrigin origin) + { + return _inner.Seek(offset, origin); + } + + public override void SetLength(long value) + { + _inner.SetLength(value); + } + + public override void Write(byte[] buffer, int offset, int count) + { + Log("Write", count, buffer, offset); + _inner.Write(buffer, offset, count); + } + + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + Log("WriteAsync", count, buffer, offset); + return _inner.WriteAsync(buffer, offset, count, cancellationToken); + } + + private void Log(string method, int count, byte[] buffer, int offset) + { + var builder = new StringBuilder($"{method}[{count}] "); + + // Write the hex + for (int i = offset; i < offset + count; i++) + { + builder.Append(buffer[i].ToString("X2")); + builder.Append(" "); + } + builder.AppendLine(); + // Write the bytes as if they were ASCII + for (int i = offset; i < offset + count; i++) + { + builder.Append((char)buffer[i]); + } + + _logger.LogVerbose(builder.ToString()); + } + } +}