Skip to content
This repository was archived by the owner on Dec 18, 2018. It is now read-only.

Implement IHttpRequestIdentifierFeature on Frame #1578

Merged
merged 1 commit into from
Apr 1, 2017

Conversation

natemcmaster
Copy link
Contributor

@natemcmaster natemcmaster commented Mar 31, 2017

This feature generates a unique ID per request. This unique ID can be used in event source and logging.

Also, this change improves KestrelEventSource by moving it back into the Kestrel.Core assembly and de-coupling from the Libuv transport. This adds two new events, RequestStart and RequestStop, which can be used to identify the correlation between connection ID and request trace
identifier.

Resolves #1367

Benchmark

-n Plaintext --kestrelThreadCount 2
dev
RPS: 1344136.86
RPS: 1374674.53

log-request-id
RPS: 1341543.62
RPS: 1360439.85


namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
{
internal static class CorrelationIdGenerator
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depending on the outcome of dotnet/extensions#197, this may move to Microsoft.Extensions.Primitives

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not leave it in HttpAbstractions? Kestrel and Hosting would have access to it there. Or does something else need it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll refer you to dotnet/extensions#197 on that one. @muratg is looking into it.

@natemcmaster
Copy link
Contributor Author

Sample log when application request has an error:

fail: Microsoft.AspNetCore.Server.Kestrel[13]
      Connection id "0HL3OB48ARG6C", Request id "0HL3OB48ARG6H": An unhandled exception was thrown by the application.

}

bool IHttpResponseFeature.HasStarted
{
get { return HasResponseStarted; }
get => HasResponseStarted;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These can be reduced to bool IHttpResponseFeature.HasStarted => HasResponseStarted; right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Will update.

@@ -95,6 +96,8 @@ public Frame(IHttpApplication<TContext> application, FrameContext frameContext)
{
try
{
KestrelEventSource.Log.RequestStart(this);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we need these events. Hosting does the exact same thing what value do we add here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hosting only logs method and path. This logs connectionId and requestId to make it possible to correlate connections and requests.

var stop = Assert.Single(events, e => e.EventName == "ConnectionStop");
Assert.All(new[] { "connectionId" }, p => Assert.Contains(p, stop.PayloadNames));
Assert.Same(KestrelEventSource.Log, stop.EventSource);
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the scopes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

avoiding copy+paste errors. Because the asserts for each event type looks so similar.

var id = Encoding.ASCII.GetBytes(context.TraceIdentifier);
Assert.Equal(IdentifierLength, id.Length);
context.Response.ContentLength = id.Length;
context.Response.Body.Write(id, 0, id.Length);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WriteAsync

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returns chunked encoding, which made it more difficult to pull the response body via StreamReader.

}))
{
var usedIds = new HashSet<string>();
var usedIdsLock = new object();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not ConcurrentBag instead of set + lock?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, i'll use that. will update

});

// requests on same connection
using (var conn = server.CreateConnection())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: call it connection since that's we call it in all other tests

// requests on same connection
using (var conn = server.CreateConnection())
{
var buff = new char[IdentifierLength];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Buff is a funny name, call it either buf or buffer 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ᕙ༼ຈل͜ຈ༽ᕗ

Assert.Equal(id, _frame.TraceIdentifier);

_frame.TraceIdentifier = null;
Assert.NotEqual(id, _frame.TraceIdentifier);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assert.NotNull again?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's implicit as we already asserted id != null.

@@ -45,6 +45,6 @@ public interface IKestrelTrace : ILogger

void NotAllConnectionsAborted();

void ApplicationError(string connectionId, Exception ex);
void ApplicationError(string connectionId, string traceIdentifier, Exception ex);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

@halter73
Copy link
Member

Would it make sense to give connection and request id's unique prefixes to make them easier to distinguish in logs?

{
public static class EventSourceExtensions
{
public static void ConnectionStart(this KestrelEventSource eventSource, Connection connection)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you are moving KestrelEventSource back to the Core project, can this change also move the connection start/stop logging to core? It would be nice if this tracing could be shared between transports.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change was required to decouple Connection which is in Transport.Libuv and KestrelEventSource which is now in Core. ConnectionStop/Start are still on KestrelEventSource which is in Kestrel.Core, but the public methods now take string arguments directly instead of Connection.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think this is worth doing now. Rather than partially moving KestrelEventSource to Core, let's move it completely. I think this might require adding some sort of OnConnectionClosed() method to IConnectionContext, but it should still be a pretty quick/simple change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

@natemcmaster
Copy link
Contributor Author

Would it make sense to give connection and request id's unique prefixes to make them easier to distinguish in logs?

@shirhatti thoughts?

Another idea I played with making each request ID contain the connection id so correlation is even easier.

// pseudocode
var count = _connectionRequests++;
var requestId = $"{_connectionId}:{count}";

@natemcmaster
Copy link
Contributor Author

🆙 📅

{
await connection.Send("GET / HTTP/1.1",
"Content-Length: 0",
"Host: localhost",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to send Content-Length and Host here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.


await connection.Receive($"HTTP/1.1 200 OK",
$"Date: {server.Context.DateHeaderValue}",
"Content-Length: " + IdentifierLength,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: use $""

@shirhatti
Copy link

Another idea I played with making each request ID contain the connection id so correlation is even easier.

YES please!

@glennc Thoughts?

@natemcmaster
Copy link
Contributor Author

@shirhatti I will follow up via email. Not everyone checks PR comments.

@@ -12,6 +12,7 @@ public interface IConnectionContext
string ConnectionId { get; }
IPipeWriter Input { get; }
IPipeReader Output { get; }
void OnConnectionClosed();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Move below "// TODO: Remove these (Use Pipes instead?)"

There is a task that will be added to Pipes that should let us track when SocketOutputConsumer complete's it's pipe reader which will be right after disposing the socket.

This feature generates a unique ID per request. This unique ID can be
used in event source and logging.

Also, this change improves KestrelEventSource by moving it back into the
Kestrel.Core assembly and de-coupling from the Libuv transport. This
adds two new events, RequestStart and RequestStop, which can be used to
identify the correlation between connection ID and request trace
identifier.
@natemcmaster natemcmaster merged commit 8395888 into aspnet:dev Apr 1, 2017
@natemcmaster natemcmaster deleted the log-request-id branch April 1, 2017 00:08
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants