Skip to content

Commit 225ed35

Browse files
authored
chore: Merge span-bridge feature branch (#3136)
1 parent 23f9887 commit 225ed35

37 files changed

Lines changed: 3450 additions & 32 deletions

.github/copilot-instructions.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,8 @@
22
- Use the latest C# language version available in the project.
33
- Ensure all files include the standard license header at the top.
44
- When writing unit tests, always use modern NUnit assertions.
5-
- We use JustMock.Lite, so use only mock configurations that are valid for that package.
5+
- We use the free version of JustMock (JustMock Lite), so use only mock configurations that are valid for that package.
66
- Always include the required usings at the top of the file.
7+
- Do not use reflection in unit tests to access private members of the class under test.
8+
- Always add [Teardown] attributes to the test class and test methods, respectively, when the test class uses disposable resources in the [Setup] method.
9+
- When refactoring code, preserve existing comments and documentation.

src/Agent/NewRelic/Agent/Core/Agent.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
using System.Threading;
3333
using System.Threading.Tasks;
3434
using NewRelic.Agent.Core.DataTransport;
35+
using NewRelic.Agent.Core.OpenTelemetryBridge;
3536

3637
namespace NewRelic.Agent.Core
3738
{
@@ -67,6 +68,7 @@ public class Agent : IAgent // any changes to api, update the interface in exten
6768
private readonly ISimpleSchedulingService _simpleSchedulingService;
6869

6970
private readonly ICustomEventTransformer _customEventTransformer;
71+
private readonly NewRelicActivitySourceProxy _activitySourceProxy;
7072

7173
public Agent(ITransactionService transactionService, ITransactionTransformer transactionTransformer,
7274
IThreadPoolStatic threadPoolStatic, ITransactionMetricNameMaker transactionMetricNameMaker, IPathHashMaker pathHashMaker,
@@ -76,7 +78,7 @@ public Agent(ITransactionService transactionService, ITransactionTransformer tra
7678
IConfigurationService configurationService, IAgentHealthReporter agentHealthReporter, IAgentTimerService agentTimerService,
7779
IMetricNameService metricNameService, Api.ITraceMetadataFactory traceMetadataFactory, ICATSupportabilityMetricCounters catMetricCounters,
7880
ILogEventAggregator logEventAggregator, ILogContextDataFilter logContextDataFilter, ISimpleSchedulingService simpleSchedulingService,
79-
ICustomEventTransformer customEventTransformer)
81+
ICustomEventTransformer customEventTransformer, NewRelicActivitySourceProxy activitySourceProxy)
8082
{
8183
_transactionService = transactionService;
8284
_transactionTransformer = transactionTransformer;
@@ -101,6 +103,8 @@ public Agent(ITransactionService transactionService, ITransactionTransformer tra
101103

102104
_customEventTransformer = customEventTransformer;
103105

106+
_activitySourceProxy = activitySourceProxy;
107+
104108
Instance = this;
105109
}
106110

@@ -595,6 +599,8 @@ public void RecordLogMessage(string frameworkName, object logEvent, Func<object,
595599
}
596600
}
597601

602+
public NewRelicActivitySourceProxy ActivitySourceProxy => _activitySourceProxy;
603+
598604
#endregion
599605

600606
#region Helpers

src/Agent/NewRelic/Agent/Core/AgentManager.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ private AgentManager()
159159
// Start the AgentHealthReporter early so that we can potentially report health issues during startup
160160
_agentHealthReporter = _container.Resolve<IAgentHealthReporter>();
161161

162+
if (Configuration.OpenTelemetryBridgeEnabled)
163+
_container.Resolve<OpenTelemetryBridge.ActivityBridge>().Start();
164+
162165
// Attempt to auto start the agent once all services have resolved, except in serverless mode
163166
if (!bootstrapConfig.ServerlessModeEnabled)
164167
_container.Resolve<IConnectionManager>().AttemptAutoStart();

src/Agent/NewRelic/Agent/Core/Configuration/DefaultConfiguration.cs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3108,6 +3108,56 @@ public string AwsAccountId
31083108
}
31093109
#endregion
31103110

3111+
#region Otel Bridge
3112+
3113+
private static readonly string[] DefaultIncludedActivitySources = ["NewRelic.Agent"];
3114+
3115+
private List<string> _includedActivitySources;
3116+
public List<string> IncludedActivitySources
3117+
{
3118+
get
3119+
{
3120+
if (_includedActivitySources == null)
3121+
{
3122+
var includedActivitySources = DefaultIncludedActivitySources.ToList();
3123+
3124+
var appSetting = TryGetAppSettingAsString("OpenTelemetry.ActivitySource.Include");
3125+
if (!string.IsNullOrEmpty(appSetting))
3126+
{
3127+
includedActivitySources.AddRange(appSetting.Split(','));
3128+
}
3129+
3130+
_includedActivitySources = includedActivitySources;
3131+
}
3132+
3133+
return _includedActivitySources;
3134+
}
3135+
}
3136+
3137+
private List<string> _excludedActivitySources;
3138+
public List<string> ExcludedActivitySources
3139+
{
3140+
get
3141+
{
3142+
if (_excludedActivitySources == null)
3143+
{
3144+
_excludedActivitySources = new List<string>();
3145+
3146+
var appSetting = TryGetAppSettingAsString("OpenTelemetry.ActivitySource.Exclude");
3147+
if (!string.IsNullOrEmpty(appSetting))
3148+
{
3149+
_excludedActivitySources.AddRange(appSetting.Split(','));
3150+
}
3151+
}
3152+
3153+
return _excludedActivitySources;
3154+
}
3155+
}
3156+
3157+
public bool OpenTelemetryBridgeEnabled => EnvironmentOverrides(TryGetAppSettingAsBoolWithDefault("OpenTelemetry.Enabled", false), "NEW_RELIC_OPEN_TELEMETRY_BRIDGE_ENABLED");
3158+
3159+
#endregion
3160+
31113161
public static bool GetLoggingEnabledValue(IEnvironment environment, configurationLog localLogConfiguration)
31123162
{
31133163
return EnvironmentOverrides(environment, localLogConfiguration.enabled, "NEW_RELIC_LOG_ENABLED");

src/Agent/NewRelic/Agent/Core/Configuration/ReportedConfiguration.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,15 @@ public IReadOnlyDictionary<string, string> GetAppSettings()
746746
public bool AwsLambdaApmModeEnabled => _configuration.AwsLambdaApmModeEnabled;
747747

748748

749+
[JsonProperty("otel_bridge.included_activity_sources")]
750+
public List<string> IncludedActivitySources => _configuration.IncludedActivitySources;
751+
752+
[JsonProperty("otel_bridge.excluded_activity_sources")]
753+
public List<string> ExcludedActivitySources => _configuration.ExcludedActivitySources;
754+
755+
[JsonProperty("otel_bridge.enabled")]
756+
public bool OpenTelemetryBridgeEnabled => _configuration.OpenTelemetryBridgeEnabled;
757+
749758
#endregion
750759
}
751760
}

src/Agent/NewRelic/Agent/Core/DependencyInjection/AgentServices.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
using NewRelic.Agent.Extensions.Providers.Wrapper;
4646
using NewRelic.Agent.Core.SharedInterfaces.Web;
4747
using NewRelic.Agent.Core.Labels;
48+
using NewRelic.Agent.Core.OpenTelemetryBridge;
4849

4950
namespace NewRelic.Agent.Core.DependencyInjection
5051
{
@@ -233,6 +234,9 @@ public static void RegisterServices(IContainer container, bool serverlessModeEna
233234

234235
container.Register<UpdatedLoadedModulesService, UpdatedLoadedModulesService>();
235236

237+
container.Register<ActivityBridge, ActivityBridge>();
238+
container.Register<NewRelicActivitySourceProxy, NewRelicActivitySourceProxy>();
239+
236240
container.Build();
237241
}
238242

src/Agent/NewRelic/Agent/Core/DistributedTracing/DistributedTracePayloadHandler.cs

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ public interface IDistributedTracePayloadHandler
3131
ITracingState AcceptDistributedTraceHeaders<T>(T carrier, Func<T, string, IEnumerable<string>> getter, TransportType transportType, DateTime transactionStartTime);
3232

3333
void InsertDistributedTraceHeaders<T>(IInternalTransaction transaction, T carrier, Action<T, string, string> setter);
34+
35+
void GetTraceFlagsAndState(IInternalTransaction transaction, out bool sampled, out string traceStateString);
3436
}
3537

3638
public class DistributedTracePayloadHandler : IDistributedTracePayloadHandler
@@ -152,23 +154,40 @@ private string FormatTraceId(string traceId)
152154

153155
private string BuildTracestate(IInternalTransaction transaction, DateTime timestamp)
154156
{
155-
var accountKey = _configurationService.Configuration.TrustedAccountKey;
156-
var version = 0;
157-
var parentType = ParentType;
158-
var parentAccountId = _configurationService.Configuration.AccountId;
159-
var appId = _configurationService.Configuration.PrimaryApplicationId;
160-
var spanId = _configurationService.Configuration.SpanEventsEnabled ? transaction.CurrentSegment.SpanId : string.Empty;
161-
var transactionId = _configurationService.Configuration.TransactionEventsEnabled ? transaction.Guid : string.Empty;
157+
// Cache configuration values to avoid repeated property access
158+
var config = _configurationService.Configuration;
159+
var accountKey = config.TrustedAccountKey;
160+
var parentAccountId = config.AccountId;
161+
var appId = config.PrimaryApplicationId;
162+
var spanEventsEnabled = config.SpanEventsEnabled;
163+
var transactionEventsEnabled = config.TransactionEventsEnabled;
164+
165+
var spanId = spanEventsEnabled ? transaction.CurrentSegment.SpanId : string.Empty;
166+
var transactionId = transactionEventsEnabled ? transaction.Guid : string.Empty;
162167
var sampled = transaction.Sampled.Value ? "1" : "0";
163-
var priority = string.Format(System.Globalization.CultureInfo.InvariantCulture, PriorityFormat, transaction.Priority);
168+
var priority = transaction.Priority.ToString("0.######", System.Globalization.CultureInfo.InvariantCulture);
164169
var timestampInMillis = timestamp.ToUnixTimeMilliseconds();
165170

166-
var newRelicTracestate = $"{accountKey}@nr={version}-{parentType}-{parentAccountId}-{appId}-{spanId}-{transactionId}-{sampled}-{priority}-{timestampInMillis}";
167-
var otherVendorTracestates = string.Empty;
168-
169-
if (transaction.TracingState?.VendorStateEntries != null && transaction.TracingState.VendorStateEntries.Any())
171+
// Use StringBuilder to avoid intermediate string allocations
172+
var sb = new System.Text.StringBuilder(128);
173+
sb.Append(accountKey)
174+
.Append("@nr=0-")
175+
.Append(ParentType).Append('-')
176+
.Append(parentAccountId).Append('-')
177+
.Append(appId).Append('-')
178+
.Append(spanId).Append('-')
179+
.Append(transactionId).Append('-')
180+
.Append(sampled).Append('-')
181+
.Append(priority).Append('-')
182+
.Append(timestampInMillis);
183+
184+
var newRelicTracestate = sb.ToString();
185+
186+
var otherVendorTracestates = String.Empty;
187+
var vendorEntries = transaction.TracingState?.VendorStateEntries;
188+
if (vendorEntries != null && vendorEntries.Any())
170189
{
171-
otherVendorTracestates = string.Join(",", transaction.TracingState.VendorStateEntries);
190+
otherVendorTracestates = string.Join(",", vendorEntries);
172191
}
173192

174193
// If otherVendorTracestates is null/empty we get a trailing comma.
@@ -177,7 +196,8 @@ private string BuildTracestate(IInternalTransaction transaction, DateTime timest
177196
return newRelicTracestate;
178197
}
179198

180-
return string.Join(",", newRelicTracestate, otherVendorTracestates);
199+
// Use string.Join only if there are vendor entries
200+
return string.Concat(newRelicTracestate, ",", otherVendorTracestates);
181201
}
182202

183203
public IDistributedTracePayload TryGetOutboundDistributedTraceApiModel(IInternalTransaction transaction, ISegment segment = null)
@@ -250,6 +270,13 @@ private IDistributedTracePayload TryGetOutboundDistributedTraceApiModelInternal(
250270
return new DistributedTraceApiModel(encodedPayload);
251271
}
252272

273+
public void GetTraceFlagsAndState(IInternalTransaction transaction, out bool sampled, out string traceStateString)
274+
{
275+
transaction.SetSampled(_adaptiveSampler);
276+
traceStateString = BuildTracestate(transaction, DateTime.UtcNow);
277+
sampled = transaction.Sampled.Value;
278+
}
279+
253280
#endregion Outgoing/Create
254281

255282
#region Incoming/Accept

0 commit comments

Comments
 (0)