Skip to content

Commit

Permalink
Merge pull request #134 from gregkalapos/ExposeContextV2
Browse files Browse the repository at this point in the history
Expose Transaction.Context.Request and Transaction.Context.Response
  • Loading branch information
gregkalapos authored Mar 4, 2019
2 parents 2363dd8 + c3d4ece commit 88e220f
Show file tree
Hide file tree
Showing 9 changed files with 194 additions and 45 deletions.
2 changes: 2 additions & 0 deletions sample/ApiSamples/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ public static void SampleError()
public static void SampleCustomTransactionWithConvenientApi() => Agent.Tracer.CaptureTransaction("TestTransaction", "TestType",
t =>
{
t.Context.Response = new Response() { Finished = true, StatusCode = 200 };
t.Context.Request = new Request("GET", new Url{Protocol = "HTTP"});
t.Tags["fooTransaction"] = "barTransaction";
Thread.Sleep(10);
t.CaptureSpan("TestSpan", "TestSpanName", s =>
Expand Down
19 changes: 9 additions & 10 deletions src/Elastic.Apm.AspNetCore/ApmMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using System.Threading.Tasks;
using Elastic.Apm.Api;
using Elastic.Apm.Helpers;
using Elastic.Apm.Model.Payload;
using Microsoft.AspNetCore.Http;

[assembly:
Expand Down Expand Up @@ -32,21 +31,21 @@ public async Task InvokeAsync(HttpContext context)
var transaction = _tracer.StartTransactionInternal($"{context.Request.Method} {context.Request.Path}",
ApiConstants.TypeRequest);

transaction.Context.Request = new Request
var url = new Url
{
Full = context.Request?.Path.Value,
HostName = context.Request.Host.Host,
Protocol = GetProtocolName(context.Request.Protocol),
Raw = context.Request?.Path.Value //TODO
};

transaction.Context.Request = new Request( context.Request.Method, url)
{
Method = context.Request.Method,
Socket = new Socket
{
Encrypted = context.Request.IsHttps,
RemoteAddress = context.Connection?.RemoteIpAddress?.ToString()
},
Url = new Url
{
Full = context.Request?.Path.Value,
HostName = context.Request.Host.Host,
Protocol = GetProtocolName(context.Request.Protocol),
Raw = context.Request?.Path.Value //TODO
},
HttpVersion = GetHttpVersion(context.Request.Protocol)
};

Expand Down
28 changes: 28 additions & 0 deletions src/Elastic.Apm/Api/Context.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;

namespace Elastic.Apm.Api
{
public class Context
{
private readonly Lazy<Dictionary<string, string>> tags = new Lazy<Dictionary<string, string>>();

/// <summary>
/// If a log record was generated as a result of a http request, the http interface can be used to collect this
/// information.
/// This property is by default null! You have to assign a <see cref="Request" /> instance to this property in order to use
/// it.
/// </summary>
public Request Request { get; set; }

/// <summary>
/// If a log record was generated as a result of a http request, the http interface can be used to collect this
/// information.
/// This property is by default null! You have to assign a <see cref="Response" /> instance to this property in order to use
/// it.
/// </summary>
public Response Response { get; set; }

public Dictionary<string, string> Tags => tags.Value;
}
}
6 changes: 6 additions & 0 deletions src/Elastic.Apm/Api/ITransaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ namespace Elastic.Apm.Api
{
public interface ITransaction
{
/// <summary>
/// Any arbitrary contextual information regarding the event, captured by the agent, optionally provided by the user.
/// This field is lazily initialized, you don't have to assign a value to it and you don't have to null check it either.
/// </summary>
Context Context { get; }

/// <summary>
/// The duration of the transaction.
/// If it's not set (its HasValue property is false) then the value
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
using Newtonsoft.Json;

namespace Elastic.Apm.Model.Payload
namespace Elastic.Apm.Api
{
internal class Request
/// <summary>
/// Encapsulates Request related information that can be attached to an <see cref="ITransaction" /> through <see cref="ITransaction.Context" />
/// See <see cref="Context.Request" />
/// </summary>
public class Request
{
public Request(string method, Url url) => (Method, Url) = (method, url);

public string HttpVersion { get; set; }

public string Method { get; set; }
public Socket Socket { get; set; }
public Url Url { get; set; }

public object Body { get; set; }
}

internal class Socket
public class Socket
{
public bool Encrypted { get; set; }

[JsonProperty("Remote_address")]
public string RemoteAddress { get; set; }
}

internal class Url
public class Url
{
public string Full { get; set; }
public string HostName { get; set; }
Expand Down
19 changes: 19 additions & 0 deletions src/Elastic.Apm/Api/Response.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Newtonsoft.Json;

namespace Elastic.Apm.Api
{
/// <summary>
/// Encapsulates Response related information that can be attached to an <see cref="ITransaction" /> through <see cref="ITransaction.Context" />
/// See <see cref="Context.Response" />
/// </summary>
public class Response
{
public bool Finished { get; set; }

/// <summary>
/// The HTTP status code of the response.
/// </summary>
[JsonProperty("Status_code")]
public int StatusCode { get; set; }
}
}
15 changes: 0 additions & 15 deletions src/Elastic.Apm/Model/Payload/Context.cs

This file was deleted.

12 changes: 0 additions & 12 deletions src/Elastic.Apm/Model/Payload/Response.cs

This file was deleted.

122 changes: 118 additions & 4 deletions test/Elastic.Apm.Tests/ApiTests/ConvenientApiTransactionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ public void SimpleActionWithReturnTypeAndExceptionAndParameter() => AssertWith1T
{
Action act = () =>
{
var result = agent.Tracer.CaptureTransaction(TransactionName, TransactionType, t =>
agent.Tracer.CaptureTransaction(TransactionName, TransactionType, t =>
{
t.Should().NotBeNull();
WaitHelpers.SleepMinimum();
Expand All @@ -175,7 +175,7 @@ public void SimpleActionWithReturnTypeAndException() => AssertWith1TransactionAn
{
Action act = () =>
{
var result = agent.Tracer.CaptureTransaction(TransactionName, TransactionType, () =>
agent.Tracer.CaptureTransaction(TransactionName, TransactionType, () =>
{
WaitHelpers.SleepMinimum();

Expand Down Expand Up @@ -310,7 +310,7 @@ public async Task AsyncTaskWithReturnTypeAndExceptionAndParameter() => await Ass
{
Func<Task> act = async () =>
{
var result = await agent.Tracer.CaptureTransaction(TransactionName, TransactionType, async t =>
await agent.Tracer.CaptureTransaction(TransactionName, TransactionType, async t =>
{
t.Should().NotBeNull();
await WaitHelpers.DelayMinimum();
Expand All @@ -335,7 +335,7 @@ public async Task AsyncTaskWithReturnTypeAndException() => await AssertWith1Tran
{
Func<Task> act = async () =>
{
var result = await agent.Tracer.CaptureTransaction(TransactionName, TransactionType, async () =>
await agent.Tracer.CaptureTransaction(TransactionName, TransactionType, async () =>
{
await WaitHelpers.DelayMinimum();

Expand Down Expand Up @@ -469,6 +469,120 @@ await t.Tracer.CaptureTransaction(TransactionName, TransactionType, async transa
payloadSender.Payloads[0].Transactions[0].Tags.Should().Contain("foo", "bar");
}

/// <summary>
/// Creates a transaction and attaches a Request to this transaction.
/// Makes sure that the transaction details are captured.
/// </summary>
[Fact]
public void TransactionWithRequest()
{
var payloadSender = AssertWith1Transaction(
n =>
{
n.Tracer.CaptureTransaction(TransactionName, TransactionType, transaction =>
{
WaitHelpers.SleepMinimum();
transaction.Context.Request = new Request("GET", new Url { Protocol = "HTTP" });
});
});

payloadSender.FirstTransaction.Context.Request.Method.Should().Be("GET");
payloadSender.FirstTransaction.Context.Request.Url.Protocol.Should().Be("HTTP");
}

/// <summary>
/// Creates a transaction and attaches a Request to this transaction. It fills all the fields on the Request.
/// Makes sure that all the transaction details are captured.
/// </summary>
[Fact]
public void TransactionWithRequestDetailed()
{
var payloadSender = AssertWith1Transaction(
n =>
{
n.Tracer.CaptureTransaction(TransactionName, TransactionType, transaction =>
{
WaitHelpers.SleepMinimum();
transaction.Context.Request = new Request("GET", new Url
{
Full = "https://elastic.co",
Raw = "https://elastic.co",
HostName = "elastic",
Protocol = "HTTP"
})
{
HttpVersion = "2.0",
Socket = new Socket
{
Encrypted = true,
RemoteAddress = "127.0.0.1"
},
Body = "123"
};
});
});

payloadSender.FirstTransaction.Context.Request.Method.Should().Be("GET");
payloadSender.FirstTransaction.Context.Request.Url.Protocol.Should().Be("HTTP");

payloadSender.FirstTransaction.Context.Request.HttpVersion.Should().Be("2.0");
payloadSender.FirstTransaction.Context.Request.Socket.Encrypted.Should().BeTrue();
payloadSender.FirstTransaction.Context.Request.Url.Full.Should().Be("https://elastic.co");
payloadSender.FirstTransaction.Context.Request.Url.Raw.Should().Be("https://elastic.co");
payloadSender.FirstTransaction.Context.Request.Socket.RemoteAddress.Should().Be("127.0.0.1");
payloadSender.FirstTransaction.Context.Request.Url.HostName.Should().Be("elastic");
payloadSender.FirstTransaction.Context.Request.Body.Should().Be("123");
}

/// <summary>
/// Creates a transaction and attaches a Response to this transaction.
/// Makes sure that the transaction details are captured.
/// </summary>
[Fact]
public void TransactionWithResponse()
{
var payloadSender = AssertWith1Transaction(
n =>
{
n.Tracer.CaptureTransaction(TransactionName, TransactionType, transaction =>
{
WaitHelpers.SleepMinimum();
transaction.Context.Response = new Response
{ Finished = true, StatusCode = 200 };
});
});

payloadSender.FirstTransaction.Context.Response.Finished.Should().BeTrue();
payloadSender.FirstTransaction.Context.Response.StatusCode.Should().Be(200);
}

/// <summary>
/// Creates a transaction and attaches a Response and a request to this transaction. It sets 1 property on each.
/// Makes sure that the transaction details are captured.
/// </summary>
[Fact]
public void TransactionWithResponseAndRequest()
{
var payloadSender = AssertWith1Transaction(
n =>
{
n.Tracer.CaptureTransaction(TransactionName, TransactionType, transaction =>
{
WaitHelpers.SleepMinimum();
transaction.Context.Response = new Response
{ Finished = true};
transaction.Context.Request = new Request("GET", new Url());

transaction.Context.Response.StatusCode = 200;
transaction.Context.Request.Url.Full = "https://elastic.co";
});
});

payloadSender.FirstTransaction.Context.Response.Finished.Should().BeTrue();
payloadSender.FirstTransaction.Context.Response.StatusCode.Should().Be(200);
payloadSender.FirstTransaction.Context.Request.Url.Full.Should().Be("https://elastic.co");
}

/// <summary>
/// Asserts on 1 transaction with async code
/// </summary>
Expand Down

0 comments on commit 88e220f

Please sign in to comment.