From be0f55142fbfacc98c47ea9314898311c21272c8 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 21 Jun 2019 19:46:24 +1200 Subject: [PATCH] Add tags to activity --- .../Internal/GrpcEventSource.cs | 2 + .../Internal/HttpContextServerCallContext.cs | 27 +++++++-- .../Grpc.AspNetCore.Server.Tests.csproj | 1 + .../HttpContextServerCallContextTests.cs | 57 +++++++++++++++++++ test/Shared/ActivityReplacer.cs | 40 +++++++++++++ 5 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 test/Shared/ActivityReplacer.cs diff --git a/src/Grpc.AspNetCore.Server/Internal/GrpcEventSource.cs b/src/Grpc.AspNetCore.Server/Internal/GrpcEventSource.cs index 65a2e79c1..46b78dcb2 100644 --- a/src/Grpc.AspNetCore.Server/Internal/GrpcEventSource.cs +++ b/src/Grpc.AspNetCore.Server/Internal/GrpcEventSource.cs @@ -52,6 +52,7 @@ internal GrpcEventSource(string eventSourceName) { } + [MethodImpl(MethodImplOptions.NoInlining)] [Event(eventId: 1, Level = EventLevel.Verbose)] public void CallStart(string method) { @@ -61,6 +62,7 @@ public void CallStart(string method) WriteEvent(1, method); } + [MethodImpl(MethodImplOptions.NoInlining)] [Event(eventId: 2, Level = EventLevel.Verbose)] public void CallStop() { diff --git a/src/Grpc.AspNetCore.Server/Internal/HttpContextServerCallContext.cs b/src/Grpc.AspNetCore.Server/Internal/HttpContextServerCallContext.cs index f3e1715cf..ec4eff854 100644 --- a/src/Grpc.AspNetCore.Server/Internal/HttpContextServerCallContext.cs +++ b/src/Grpc.AspNetCore.Server/Internal/HttpContextServerCallContext.cs @@ -175,9 +175,8 @@ private void ProcessHandlerError(Exception ex, string method) { HttpContext.Response.ConsolidateTrailers(this); } - - GrpcEventSource.Log.CallFailed(_status.StatusCode); - GrpcEventSource.Log.CallStop(); + + LogCallEnd(); _callComplete = true; } @@ -260,11 +259,25 @@ private void EndCallCore() HttpContext.Response.ConsolidateTrailers(this); } - GrpcEventSource.Log.CallStop(); + LogCallEnd(); _callComplete = true; } + private void LogCallEnd() + { + var currentActivity = Activity.Current; + if (currentActivity != null) + { + currentActivity.AddTag("GrpcStatus", _status.StatusCode.ToTrailerString()); + } + if (_status.StatusCode != StatusCode.OK) + { + GrpcEventSource.Log.CallFailed(_status.StatusCode); + } + GrpcEventSource.Log.CallStop(); + } + protected override WriteOptions? WriteOptionsCore { get; set; } protected override AuthContext AuthContextCore @@ -329,6 +342,12 @@ protected override Task WriteResponseHeadersAsyncCore(Metadata responseHeaders) public void Initialize() { + var currentActivity = Activity.Current; + if (currentActivity != null) + { + Activity.Current.AddTag("GrpcMethod", MethodCore); + } + GrpcEventSource.Log.CallStart(MethodCore); var timeout = GetTimeout(); diff --git a/test/Grpc.AspNetCore.Server.Tests/Grpc.AspNetCore.Server.Tests.csproj b/test/Grpc.AspNetCore.Server.Tests/Grpc.AspNetCore.Server.Tests.csproj index ca8b838dd..0cc36f792 100644 --- a/test/Grpc.AspNetCore.Server.Tests/Grpc.AspNetCore.Server.Tests.csproj +++ b/test/Grpc.AspNetCore.Server.Tests/Grpc.AspNetCore.Server.Tests.csproj @@ -8,6 +8,7 @@ + diff --git a/test/Grpc.AspNetCore.Server.Tests/HttpContextServerCallContextTests.cs b/test/Grpc.AspNetCore.Server.Tests/HttpContextServerCallContextTests.cs index adb4d6502..986353025 100644 --- a/test/Grpc.AspNetCore.Server.Tests/HttpContextServerCallContextTests.cs +++ b/test/Grpc.AspNetCore.Server.Tests/HttpContextServerCallContextTests.cs @@ -18,6 +18,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Net; using System.Security.Cryptography.X509Certificates; @@ -608,6 +609,62 @@ private async Task LongRunningDeadlineAbort_WaitsUntilDeadlineAbortIsFinished(st Assert.IsTrue(serverCallContext._callComplete); } + [Test] + public void Initialize_MethodInPath_SetsMethodOnActivity() + { + using (new ActivityReplacer()) + { + // Arrange + var httpContext = new DefaultHttpContext(); + httpContext.Request.Path = "/Package.Service/Method"; + var context = CreateServerCallContext(httpContext); + + // Act + context.Initialize(); + + // Assert + Assert.AreEqual("/Package.Service/Method", Activity.Current.Tags.Single(t => t.Key == "GrpcMethod").Value); + } + } + + [Test] + public async Task EndCallAsync_StatusSet_SetsStatusOnActivity() + { + using (new ActivityReplacer()) + { + // Arrange + var httpContext = new DefaultHttpContext(); + var context = CreateServerCallContext(httpContext); + context.Status = new Status(StatusCode.ResourceExhausted, string.Empty); + + // Act + context.Initialize(); + await context.EndCallAsync(); + + // Assert + Assert.AreEqual("8", Activity.Current.Tags.Single(t => t.Key == "GrpcStatus").Value); + } + } + + [Test] + public async Task ProcessHandlerErrorAsync_Exception_SetsStatusOnActivity() + { + using (new ActivityReplacer()) + { + // Arrange + var httpContext = new DefaultHttpContext(); + var context = CreateServerCallContext(httpContext); + context.Status = new Status(StatusCode.ResourceExhausted, string.Empty); + + // Act + context.Initialize(); + await context.ProcessHandlerErrorAsync(new Exception(), "MethodName"); + + // Assert + Assert.AreEqual("2", Activity.Current.Tags.Single(t => t.Key == "GrpcStatus").Value); + } + } + private HttpContextServerCallContext CreateServerCallContext(HttpContext httpContext, ILogger? logger = null) { return new HttpContextServerCallContext(httpContext, new GrpcServiceOptions(), logger ?? NullLogger.Instance); diff --git a/test/Shared/ActivityReplacer.cs b/test/Shared/ActivityReplacer.cs new file mode 100644 index 000000000..3d34422cf --- /dev/null +++ b/test/Shared/ActivityReplacer.cs @@ -0,0 +1,40 @@ +#region Copyright notice and license + +// Copyright 2019 The gRPC Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +using System; +using System.Diagnostics; + +namespace Grpc.Tests.Shared +{ + public class ActivityReplacer : IDisposable + { + private readonly Activity _activity; + + public ActivityReplacer() + { + _activity = new Activity("Test"); + _activity.Start(); + } + + public void Dispose() + { + Debug.Assert(Activity.Current == _activity); + _activity.Stop(); + } + } +}