Unable to specify Environment value when using APM in .NET with AddOtlpExporter

My application sends traces to the Elastic APM (v.8.1) using .NET OpenTelemetry OTLP Exporter:

builder.AddOtlpExporter(o =>
{
  o.Endpoint = new Uri("...");
  o.Headers = "Authorization=Bearer ...";
}

It works fines and traces are sent to the specified Elastic APM endpoint URL.
I also need to distinguish between deployment environments (test, stage, prod), this is done by setting hostContext.HostingEnvironmentName to the respective value. But the value is ignored by the Elastic APM. I tried other alternative like Environment.SetEnvironmentVariable("ELASTIC_APM_ENVIRONMENT", "test"), but nothing worked.

I know if I import ElasticAPM NuGet packages it will do this for me, but I would like to stay with bare OTLP Exporter. Is it possible to specify environment name in that case?

Hi @Vagif_Abilov,

for this, you'll need to configure the .NET OpenTelemetry OTLP Exporter to send the environment. It's worth to keep in mind that that component is not from Elastic, so that's why the ELASTIC_APM_ENVIRONMENT is ignored - that is known to the Elastic .NET APM Agent (and other Elastic APM Agents) - but not for the OpenTelemetry agent.

I think you can do this with the .NET OTel libraries as well. Could you try something like this?

var resourceBuilder = ResourceBuilder
                .CreateDefault()
                .AddAttributes(new Dictionary<string, object>
                {
                    {"environment", "production"}
                });

and then pass it to the exporter, like this:

X.AddOpenTelemetryTracing(i => i.SetResourceBuilder(resourceBuilder)); //don't forget to add other things you need...

Hi @GregKalapos,

Thank you for a quick response. Yes, I already tried that but it didn't work. Here's my code (in F# but very similar to C# code):

        let resource =
            ResourceBuilder
                .CreateDefault()
                .AddService(Assembly.GetEntryAssembly().GetName().Name, serviceInstanceId = $"{Dns.GetHostName()}")
                .AddAttributes(dict ["environment", box "production"])
        let builder =
            builder
                .AddSource(getAssemblyName ())
                .SetResourceBuilder(resource)
                .AddAspNetCoreInstrumentation()
                .AddHttpClientInstrumentation()
                .AddSqlClientInstrumentation(fun o ->
                    o.SetDbStatementForText <- true
                    o.RecordException <- true)
                .AddOtlpExporter(fun o ->
                    o.Protocol <- OpenTelemetry.Exporter.OtlpExportProtocol.Grpc
                    o.Endpoint <- Uri(elasticSettings.["Endpoint"])
                    o.Headers <- "Authorization=Bearer " + elasticSettings.["SecretToken"])

Hey Vagif!

I think the correct semantic convention attribute to apply here is deployment.environment see:

And here where we read that value in apm-server apm-server/metadata.go at f2278410fabe38b0a12c0f88d62e7c2871084001 · elastic/apm-server · GitHub

1 Like

Bingo! That did the trick. deployment_environment is the anwer.

Thanks you @Martijn_Laarman and @GregKalapos

This topic was automatically closed 20 days after the last reply. New replies are no longer allowed.