Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions OpenTelemetry.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@
<Folder Name="/examples/">
<Project Path="examples/AspNetCore/Examples.AspNetCore.csproj" />
<Project Path="examples/Console/Examples.Console.csproj" />
<Project Path="examples/EnvironmentVariables/Examples.EnvironmentVariables.csproj" />
<Project Path="examples/FSharp/Examples.FSharp.fsproj" />
<Project Path="examples/GrpcService/Examples.GrpcService.csproj" />
</Folder>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>$(DefaultTargetFrameworkForExampleApps)</TargetFramework>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Api\OpenTelemetry.Api.csproj" />
</ItemGroup>

</Project>
235 changes: 235 additions & 0 deletions examples/EnvironmentVariables/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

using System.Collections;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using OpenTelemetry;
using OpenTelemetry.Context.Propagation;

namespace Examples.EnvironmentVariables;

internal static class Program
{
private const string ActivitySourceName = "Examples.EnvironmentVariables";
private const string ChildModeArgument = "--child";

private static readonly ActivitySource ActivitySource = new(ActivitySourceName);
private static readonly TextMapPropagator Propagator = new CompositeTextMapPropagator(
[
new TraceContextPropagator(),
new BaggagePropagator(),
]);

public static int Main(string[] args)
{
using var listener = CreateActivityListener();

return args.Contains(ChildModeArgument, StringComparer.Ordinal)
? RunAsChild()
: RunAsParent();
}

private static int RunAsParent()
{
Baggage.ClearBaggage();
Baggage.SetBaggage("tenant.id", "contoso");
Baggage.SetBaggage("user.id", "alice");

using var activity = ActivitySource.StartActivity("parent-process");
if (activity == null)
{
Console.Error.WriteLine("Failed to create the parent activity.");
return 1;
}

WriteProcessContext("Parent", activity, default, Baggage.Current);

var startInfo = CreateChildStartInfo();

CopyCurrentEnvironment(startInfo.Environment);

var context = new PropagationContext(activity.Context, Baggage.Current);
Propagator.Inject(context, startInfo.Environment, EnvironmentVariableCarrier.Set);

Console.WriteLine("[Parent] Injected environment variables:");
WritePropagationFields("Parent", startInfo.Environment);
Console.WriteLine();

using var child = Process.Start(startInfo);
if (child == null)
{
Console.Error.WriteLine("Failed to start the child process.");
return 1;
}

var childStandardOutput = child.StandardOutput.ReadToEnd();
var childStandardError = child.StandardError.ReadToEnd();

child.WaitForExit();
Comment thread
martincostello marked this conversation as resolved.
Outdated
Comment thread
martincostello marked this conversation as resolved.
Outdated

if (!string.IsNullOrEmpty(childStandardOutput))
{
Console.WriteLine();
Console.Write(childStandardOutput);
}

if (!string.IsNullOrEmpty(childStandardError))
{
Console.Error.Write(childStandardError);
}

Console.WriteLine();
Console.WriteLine($"[Parent] Child process exited with code {child.ExitCode}.");

return child.ExitCode;
}

private static int RunAsChild()
{
var carrier = EnvironmentVariableCarrier.Capture();
var parentContext = Propagator.Extract(default, carrier, EnvironmentVariableCarrier.Get);

Baggage.Current = parentContext.Baggage;

using var activity = ActivitySource.StartActivity(
"child-process",
ActivityKind.Internal,
parentContext.ActivityContext);

if (activity == null)
{
Console.Error.WriteLine("Failed to create the child activity.");
return 1;
}

Console.WriteLine(" [Child] Captured propagated environment variables:");
WritePropagationFields("Child", carrier);
Console.WriteLine();

WriteProcessContext("Child", activity, parentContext.ActivityContext, Baggage.Current);

return 0;
}

private static ActivityListener CreateActivityListener()
{
var listener = new ActivityListener
{
ShouldListenTo = static source => source.Name == ActivitySourceName,
Sample = static (ref _) => ActivitySamplingResult.AllDataAndRecorded,
SampleUsingParentId = static (ref _) => ActivitySamplingResult.AllDataAndRecorded,
};

ActivitySource.AddActivityListener(listener);

return listener;
}

private static ProcessStartInfo CreateChildStartInfo()
{
var processPath = Environment.ProcessPath
?? throw new InvalidOperationException("The current process path is unavailable.");

var entryAssemblyPath = Assembly.GetEntryAssembly()?.Location
?? throw new InvalidOperationException("The entry assembly path is unavailable.");

var fileName = Path.GetFileNameWithoutExtension(processPath);

var startInfo = new ProcessStartInfo(processPath)
{
RedirectStandardError = true,
RedirectStandardOutput = true,
UseShellExecute = false,
};

if (string.Equals(fileName, "dotnet", StringComparison.OrdinalIgnoreCase))
{
startInfo.ArgumentList.Add(entryAssemblyPath);
}

startInfo.ArgumentList.Add(ChildModeArgument);

return startInfo;
}

private static void CopyCurrentEnvironment(IDictionary<string, string?> environment)
{
foreach (DictionaryEntry variable in Environment.GetEnvironmentVariables())
{
environment[(string)variable.Key] = variable.Value?.ToString();
}
}

private static void WriteProcessContext(
string role,
Activity activity,
ActivityContext parentContext,
Baggage baggage)
{
string indent = role is "Parent" ? string.Empty : " ";

Console.WriteLine($"{indent}[{role}] ProcessId: {Environment.ProcessId}");
Console.WriteLine($"{indent}[{role}] TraceId: {activity.TraceId}");
Console.WriteLine($"{indent}[{role}] SpanId: {activity.SpanId}");
Console.WriteLine($"{indent}[{role}] ParentSpanId: {FormatSpanId(activity.ParentSpanId)}");

if (parentContext != default)
{
Console.WriteLine($"{indent}[{role}] ExtractedParentTraceId: {parentContext.TraceId}");
Console.WriteLine($"{indent}[{role}] ExtractedParentSpanId: {FormatSpanId(parentContext.SpanId)}");
}

Console.WriteLine($"{indent}[{role}] Baggage: {FormatBaggage(baggage)}");

static string FormatSpanId(ActivitySpanId spanId)
{
return spanId == default ? "<none>" : spanId.ToString();
}

static string FormatBaggage(Baggage baggage)
{
if (baggage.Count == 0)
{
return "<empty>";
}

var builder = new StringBuilder();

foreach (var item in baggage.GetBaggage())
{
if (builder.Length > 0)
{
builder.Append(", ");
}

builder.Append(item.Key);
builder.Append('=');
builder.Append(item.Value);
}

return builder.ToString();
}
}

private static void WritePropagationFields<T>(string role, T carrier)
where T : IEnumerable<KeyValuePair<string, string?>>
{
if (Propagator.Fields is not { Count: > 0 } fields)
{
return;
}

string indent = role is "Parent" ? string.Empty : " ";

foreach (var field in fields)
{
var normalized = EnvironmentVariableCarrier.NormalizeKey(field);
var values = EnvironmentVariableCarrier.Get(carrier, field);
var value = values?.FirstOrDefault();

Console.WriteLine($"{indent}[{role}] {normalized}={value ?? "<not set>"}");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
OpenTelemetry.Context.Propagation.EnvironmentVariableCarrier
static OpenTelemetry.Context.Propagation.EnvironmentVariableCarrier.Capture() -> System.Collections.Generic.IReadOnlyDictionary<string!, string?>!
static OpenTelemetry.Context.Propagation.EnvironmentVariableCarrier.Capture(System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string!, string?>>! environmentVariables) -> System.Collections.Generic.IReadOnlyDictionary<string!, string?>!
static OpenTelemetry.Context.Propagation.EnvironmentVariableCarrier.Get<T>(T carrier, string! key) -> System.Collections.Generic.IEnumerable<string!>?
static OpenTelemetry.Context.Propagation.EnvironmentVariableCarrier.NormalizeKey(string! key) -> string!
static OpenTelemetry.Context.Propagation.EnvironmentVariableCarrier.Set<T>(T carrier, string! key, string! value) -> void
Loading
Loading