question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Add Additional Standard Tags in AddAspNetCoreInstrumentation

See original GitHub issue

Feature Request

Is your feature request related to a problem?

There are several tags specified by the open telemetry specification that are not logged by default in AddAspNetCoreInstrumentation. It would be nice if we could add an option to log them too.

Describe the solution you’d like:

I currently use the following code to log them:

options.Enrich = (activity, eventName, obj) =>
{
    if (obj is HttpRequest request)
    {
        var context = request.HttpContext;
        activity.AddTag(OpenTelemetryAttributeName.Http.Flavor, GetHttpFlavour(request.Protocol));
        activity.AddTag(OpenTelemetryAttributeName.Http.Scheme, request.Scheme);
        activity.AddTag(OpenTelemetryAttributeName.Http.ClientIP, context.Connection.RemoteIpAddress);
        activity.AddTag(OpenTelemetryAttributeName.Http.RequestContentLength, request.ContentLength);
        activity.AddTag(OpenTelemetryAttributeName.Http.RequestContentType, request.ContentType);

        var user = context.User;
        if (user.Identity?.Name is not null)
        {
            activity.AddTag(OpenTelemetryAttributeName.EndUser.Id, user.Identity.Name);
            activity.AddTag(OpenTelemetryAttributeName.EndUser.Scope, string.Join(',', user.Claims.Select(x => x.Value)));
        }
    }
    else if (obj is HttpResponse response)
    {
        activity.AddTag(OpenTelemetryAttributeName.Http.ResponseContentLength, response.ContentLength);
        activity.AddTag(OpenTelemetryAttributeName.Http.ResponseContentType, response.ContentType);
    }

    static string GetHttpFlavour(string protocol)
    {
        if (HttpProtocol.IsHttp10(protocol))
        {
            return HttpFlavour.Http10;
        }
        else if (HttpProtocol.IsHttp11(protocol))
        {
            return HttpFlavour.Http11;
        }
        else if (HttpProtocol.IsHttp2(protocol))
        {
            return HttpFlavour.Http20;
        }
        else if (HttpProtocol.IsHttp3(protocol))
        {
            return HttpFlavour.Http30;
        }

        throw new InvalidOperationException($"Protocol {protocol} not recognised.");
    }
};

/// <summary>
/// Constants for semantic attribute names outlined by the OpenTelemetry specifications.
/// <see href="https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/README.md"/>.
/// </summary>
public static class OpenTelemetryAttributeName
{
    /// <summary>
    /// Constants for deployment semantic attribute names outlined by the OpenTelemetry specifications.
    /// <see href="https://github.com/open-telemetry/opentelemetry-specification/blob/11cc73939a32e3a2e6f11bdeab843c61cf8594e9/specification/resource/semantic_conventions/deployment_environment.md"/>.
    /// </summary>
    public static class Deployment
    {
        /// <summary>
        /// The name of the deployment environment (aka deployment tier).
        /// </summary>
        /// <example>staging; production.</example>
        public const string Environment = "deployment.environment";
    }

    /// <summary>
    /// Constants for end user semantic attribute names outlined by the OpenTelemetry specifications.
    /// <see href="https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/span-general.md"/>.
    /// </summary>
    public static class EndUser
    {
        /// <summary>
        /// Username or client_id extracted from the access token or Authorization header in the inbound request from outside the system.
        /// </summary>
        /// <example>E.g. username.</example>
        public const string Id = "enduser.id";

        /// <summary>
        /// Actual/assumed role the client is making the request under extracted from token or application security context.
        /// </summary>
        /// <example>E.g. admin.</example>
        public const string Role = "enduser.role";

        /// <summary>
        /// Scopes or granted authorities the client currently possesses extracted from token or application security context.
        /// The value would come from the scope associated with an OAuth 2.0 Access Token or an attribute value in a SAML 2.0 Assertion.
        /// </summary>
        /// <example>E.g. read:message,write:files.</example>
        public const string Scope = "enduser.scope";
    }

    /// <summary>
    /// Constants for HTTP semantic attribute names outlined by the OpenTelemetry specifications.
    /// <see href="https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/http.md"/>.
    /// </summary>
    public static class Http
    {
        /// <summary>
        /// The URI scheme identifying the used protocol.
        /// </summary>
        /// <example>E.g. http or https.</example>
        public const string Scheme = "http.scheme";

        /// <summary>
        /// Kind of HTTP protocol used.
        /// </summary>
        /// <example>E.g. 1.0, 1.1, 2.0, SPDY or QUIC.</example>
        public const string Flavor = "http.flavor";

        /// <summary>
        /// The IP address of the original client behind all proxies, if known (e.g. from X-Forwarded-For).
        /// </summary>
        /// <example>E.g. 83.164.160.102.</example>
        public const string ClientIP = "http.client_ip";

        /// <summary>
        /// The size of the request payload body in bytes. This is the number of bytes transferred excluding headers and is often,
        /// but not always, present as the Content-Length header. For requests using transport encoding, this should be the
        /// compressed size.
        /// </summary>
        /// <example>E.g. 3495.</example>
        public const string RequestContentLength = "http.request_content_length";

        /// <summary>
        /// The content type of the request body.
        /// </summary>
        /// <example>E.g. application/json.</example>
        public const string RequestContentType = "http.request_content_type";

        /// <summary>
        /// The size of the response payload body in bytes. This is the number of bytes transferred excluding headers and is often,
        /// but not always, present as the Content-Length header. For requests using transport encoding, this should be the
        /// compressed size.
        /// </summary>
        /// <example>E.g. 3495.</example>
        public const string ResponseContentLength = "http.response_content_length";

        /// <summary>
        /// The content type of the response body.
        /// </summary>
        /// <example>E.g. application/json.</example>
        public const string ResponseContentType = "http.response_content_type";
    }

    /// <summary>
    /// Constants for host semantic attribute names outlined by the OpenTelemetry specifications.
    /// <see href="https://github.com/open-telemetry/opentelemetry-specification/blob/11cc73939a32e3a2e6f11bdeab843c61cf8594e9/specification/resource/semantic_conventions/host.md"/>.
    /// </summary>
    public static class Host
    {
        /// <summary>
        /// Name of the host. On Unix systems, it may contain what the hostname command returns, or the fully qualified hostname,
        /// or another name specified by the user.
        /// </summary>
        /// <example>E.g. opentelemetry-test.</example>
        public const string Name = "host.name";
    }

    /// <summary>
    /// Constants for service semantic attribute names outlined by the OpenTelemetry specifications.
    /// <see href="https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/messaging.md"/>.
    /// </summary>
    public static class Service
    {
        /// <summary>
        /// The name of the service sending messages.
        /// </summary>
        public const string Name = "service.name";
    }
}

Describe alternatives you’ve considered.

Currently using the Enrich method to add additional tags but would be nice if these were built in as they are defined by the specification.

Issue Analytics

  • State:open
  • Created 2 years ago
  • Reactions:4
  • Comments:5 (1 by maintainers)

github_iconTop GitHub Comments

2reactions
dgarciarubiocommented, Jun 29, 2021
options.Enrich = (activity, eventName, obj) =>
{
    if (obj is HttpRequest request)
    {
        var user = request.HttpContext.User;
        if (user.Identity?.Name is not null)
        {
            activity.AddTag(OpenTelemetryAttributeName.EndUser.Id, user.Identity.Name);
            activity.AddTag(OpenTelemetryAttributeName.EndUser.Scope, string.Join(',', user.Claims.Select(x => x.Value)));
        }
    }
};

Is it even possible to obtain properties of the user this way? I’m just trying to do the same thing and the identity comes always empty, as if at the point where the Enrich callback is called it wasn’t populated yet.

0reactions
RichiCoder1commented, Jul 8, 2022

Is it even possible to obtain properties of the user this way? I’m just trying to do the same thing and the identity comes always empty, as if at the point where the Enrich callback is called it wasn’t populated yet.

I’m finding the same thing – just commented over here about it: RehanSaeed/rehansaeed.github.io#607 (comment)

TL;DR The “OnStartActivity” callback is way too early to successfully resolve an authenticated User principal. Authentication middleware hasn’t executed yet.

Would love to have some guidance from the experts here about how to enrich telemetry data with “enduser” values. This seems like a common use-case – something I’d expect to be a solved problem in the AspNetCore Instrumentation library.

The specification recommends propagating “enduser” values via the Baggage mechanism.

To accomplish that today, it seems I’d need to write some custom middleware that runs immediately after Authentication – to copy the authenticated User (“enduser”) data into Baggage – and then add processors like this sample one to enrich telemetry data from Baggage. Is that the recommendation here?

A rather janky solution for this might be an EnrichOpenTelemetryIdentity that runs after UseAuthentication and looks for the IHttpAuthenticationFeature feature in context and hydrates from that. I don’t see a way to automatically plug that info however.

Read more comments on GitHub >

github_iconTop Results From Across the Web

NET OpenTelemetry auto-instrumentation
Additional attributes can be added here as comma-separated key=value pairs. Add the deployment.environment=[environment-name] tag as needed ...
Read more >
OpenTelemetry .NET: All you need to know
Add attributes to your span by calling activity.SetTag . Attributes are key-value pairs which add indexes and operation-level information to ...
Read more >
Tracing .NET Applications Easily With OpenTelemetry
In this article, we take a look at tracing in .NET applications, starting with the auto-instrumentation libraries and then custom traces.
Read more >
Instrumenting C# .Net Apps With OpenTelemetry
To do this, we can add additional spans manually over sections of the code. Note that OpenTelemetry .NET maintains compatibility with existing .NET...
Read more >
NET Observability with OpenTelemetry
OpenTelemetry (OTel) is a cross-platform, open standard for collecting and emitting ... Add(1); // Add a tag to the Activity activity?
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found