Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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: 0 additions & 1 deletion tracer/missing-nullability-files.csv
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ src/Datadog.Trace/DatabaseMonitoring/MultiPartIdentifier.cs
src/Datadog.Trace/DatabaseMonitoring/VendoredSqlHelpers.cs
src/Datadog.Trace/DataStreamsMonitoring/CheckpointKind.cs
src/Datadog.Trace/DiagnosticListeners/AspNetCoreDiagnosticObserver.cs
src/Datadog.Trace/DiagnosticListeners/DiagnosticManager.cs
src/Datadog.Trace/DiagnosticListeners/DiagnosticObserver.cs
src/Datadog.Trace/DiagnosticListeners/EndpointFeatureProxy.cs
src/Datadog.Trace/DiagnosticListeners/IDiagnosticManager.cs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,6 @@
<type fullname="System.Diagnostics.ActivityIdFormat" />
<type fullname="System.Diagnostics.ActivitySpanId" />
<type fullname="System.Diagnostics.ActivityTraceId" />
<type fullname="System.Diagnostics.DiagnosticListener" />
<type fullname="System.Diagnostics.DistributedContextPropagator" />
<type fullname="System.Diagnostics.Metrics.Instrument" />
<type fullname="System.Diagnostics.Metrics.MeasurementCallback`1" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ internal static void SetActivityKind(IActivity5 activity)
ActivityListener.SetActivityKind(activity, GetActivityKind(activity));
}

internal static void EnhanceActivityMetadata(IActivity5 activity)
internal static void EnhanceActivityMetadata5(IActivity5 activity)
{
activity.AddTag("operation.name", activity.DisplayName);
var jobName = activity.Tags.FirstOrDefault(kv => kv.Key == "job.name").Value ?? string.Empty;
Expand All @@ -54,6 +54,22 @@ internal static void EnhanceActivityMetadata(IActivity5 activity)
activity.DisplayName = CreateResourceName(activity.DisplayName, jobName);
}

internal static void EnhanceActivityMetadata(IActivity activity)
{
if (activity is IActivity5 activity5)
{
EnhanceActivityMetadata5(activity5);
return;
}

if (activity.OperationName is not null)
{
// enhancing span metadata for < IActivity5
activity.AddTag("operation.name", activity.OperationName);
activity.AddTag("resource.name", CreateResourceName(activity.OperationName, activity.Tags.FirstOrDefault(kv => kv.Key == "job.name").Value ?? string.Empty));
}
}

internal static void AddException(object exceptionArg, IActivity activity)
{
if (exceptionArg is not Exception exception)
Expand All @@ -67,5 +83,47 @@ internal static void AddException(object exceptionArg, IActivity activity)
activity.AddTag(Tags.ErrorStack, exception.ToString());
activity.AddTag("otel.status_code", "STATUS_CODE_ERROR");
}

/// <summary>
/// Handles Quartz diagnostic events.
/// This method is shared between the DiagnosticObserver (modern .NET) and reflection-based observer (.NET Framework).
/// </summary>
internal static void HandleDiagnosticEvent(string eventName, object arg)
{
switch (eventName)
{
case "Quartz.Job.Execute.Start":
case "Quartz.Job.Veto.Start":
var activity = ActivityListener.GetCurrentActivity();
if (activity is IActivity5 activity5)
{
SetActivityKind(activity5);
}
else
{
Log.Debug("The activity was not Activity5 (Less than .NET 5.0). Unable to set span kind.");
}

if (activity?.Instance is not null)
{
EnhanceActivityMetadata(activity);
}

break;
case "Quartz.Job.Execute.Stop":
case "Quartz.Job.Veto.Stop":
break;
case "Quartz.Job.Execute.Exception":
case "Quartz.Job.Veto.Exception":
// setting an exception manually
var closingActivity = ActivityListener.GetCurrentActivity();
if (closingActivity?.Instance is not null)
{
AddException(arg, closingActivity);
}

break;
}
}
}
}
4 changes: 2 additions & 2 deletions tracer/src/Datadog.Trace/ClrProfiler/Instrumentation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,6 @@ private static void InitializeTracer(Stopwatch sw)
}
}

#if !NETFRAMEWORK
private static void StartDiagnosticManager()
{
var observers = new List<DiagnosticObserver>();
Expand All @@ -484,15 +483,16 @@ private static void StartDiagnosticManager()
}
else
{
#if !NETFRAMEWORK
observers.Add(new AspNetCoreDiagnosticObserver());
#endif
observers.Add(new QuartzDiagnosticObserver());
}

var diagnosticManager = new DiagnosticManager(observers);
diagnosticManager.Start();
DiagnosticManager.Instance = diagnosticManager;
}
#endif

private static void InitializeDebugger(TracerSettings tracerSettings)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// <copyright file="DiagnosticListenerObserverFactory.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>

#nullable enable

using System;
using System.Reflection;
using System.Reflection.Emit;
using Datadog.Trace.DuckTyping;
using Datadog.Trace.Logging;

namespace Datadog.Trace.DiagnosticListeners
{
/// <summary>
/// This is code written by Cursor to do the reflection needed for DiagnosticListener.
/// Factory for creating dynamic observer types that can subscribe to DiagnosticListener.AllListeners.
/// Uses Reflection.Emit to generate types at runtime that implement IObserver&lt;DiagnosticListener&gt;
/// without directly referencing the DiagnosticSource assembly.
/// </summary>
internal static class DiagnosticListenerObserverFactory
{
private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor(typeof(DiagnosticListenerObserverFactory));

/// <summary>
/// Creates a dynamic type that implements IObserver&lt;DiagnosticListener&gt;
/// to receive notifications about new DiagnosticListeners.
/// </summary>
/// <param name="diagnosticListenerType">The Type of DiagnosticListener obtained via reflection</param>
/// <returns>A dynamically created Type that implements IObserver&lt;DiagnosticListener&gt;, or null if creation fails</returns>
public static Type? CreateObserverType(Type diagnosticListenerType)
{
try
{
// Get the IObserver<DiagnosticListener> type
var observerType = typeof(IObserver<>).MakeGenericType(diagnosticListenerType);

// Create a dynamic assembly to hold our observer type
var assemblyName = new AssemblyName("Datadog.DiagnosticManager.Dynamic");
assemblyName.Version = typeof(DiagnosticManager).Assembly.GetName().Version;
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
var moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");

// Ensure type visibility for DuckType infrastructure
DuckType.EnsureTypeVisibility(moduleBuilder, typeof(DiagnosticManager));

// Define the observer type that will implement IObserver<DiagnosticListener>
var typeBuilder = moduleBuilder.DefineType(
"DiagnosticListenerObserver",
TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.AutoClass | TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit | TypeAttributes.AutoLayout | TypeAttributes.Sealed,
typeof(object),
new[] { observerType });

// Add a field to hold the DiagnosticManager instance that will handle callbacks
var managerField = typeBuilder.DefineField("_manager", typeof(DiagnosticManager), FieldAttributes.Private | FieldAttributes.InitOnly);

// Define constructor that takes DiagnosticManager as parameter
CreateConstructor(typeBuilder, managerField);

// Define the three IObserver methods
CreateOnCompletedMethod(typeBuilder);
CreateOnErrorMethod(typeBuilder);
var success = CreateOnNextMethod(typeBuilder, managerField, diagnosticListenerType);

if (!success)
{
return null;
}

// Create and return the final type
var createdType = typeBuilder.CreateTypeInfo()?.AsType();
return createdType;
}
catch (Exception ex)
{
Log.Error(ex, "Error creating dynamic DiagnosticListener observer type");
return null;
}
}

/// <summary>
/// Creates the constructor for the observer type.
/// Constructor signature: .ctor(DiagnosticManager manager)
/// </summary>
private static void CreateConstructor(TypeBuilder typeBuilder, FieldInfo managerField)
{
var constructor = typeBuilder.DefineConstructor(
MethodAttributes.Public,
CallingConventions.Standard,
new[] { typeof(DiagnosticManager) });

var ctorIl = constructor.GetILGenerator();

// Call base object constructor
ctorIl.Emit(OpCodes.Ldarg_0);
var baseConstructor = typeof(object).GetConstructor(Type.EmptyTypes);
if (baseConstructor is null)
{
throw new NullReferenceException("Could not get Object constructor.");
}

ctorIl.Emit(OpCodes.Call, baseConstructor);

// Store the manager field: this._manager = manager;
ctorIl.Emit(OpCodes.Ldarg_0);
ctorIl.Emit(OpCodes.Ldarg_1);
ctorIl.Emit(OpCodes.Stfld, managerField);
ctorIl.Emit(OpCodes.Ret);
}

/// <summary>
/// Creates the OnCompleted method (no-op implementation).
/// </summary>
private static void CreateOnCompletedMethod(TypeBuilder typeBuilder)
{
var methodAttributes = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final | MethodAttributes.HideBySig;
var onCompletedMethod = typeBuilder.DefineMethod("OnCompleted", methodAttributes, typeof(void), Type.EmptyTypes);
var il = onCompletedMethod.GetILGenerator();
il.Emit(OpCodes.Ret);
}

/// <summary>
/// Creates the OnError method (no-op implementation).
/// </summary>
private static void CreateOnErrorMethod(TypeBuilder typeBuilder)
{
var methodAttributes = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final | MethodAttributes.HideBySig;
var onErrorMethod = typeBuilder.DefineMethod("OnError", methodAttributes, typeof(void), new[] { typeof(Exception) });
var il = onErrorMethod.GetILGenerator();
il.Emit(OpCodes.Ret);
}

/// <summary>
/// Creates the OnNext method that forwards to DiagnosticManager.OnDiagnosticListenerNext.
/// Method signature: void OnNext(DiagnosticListener listener)
/// Implementation: this._manager.OnDiagnosticListenerNext(listener);
/// </summary>
private static bool CreateOnNextMethod(TypeBuilder typeBuilder, FieldInfo managerField, Type diagnosticListenerType)
{
var methodAttributes = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final | MethodAttributes.HideBySig;

// Find the callback method on DiagnosticManager
var onDiagnosticListenerNextMethodInfo = typeof(DiagnosticManager).GetMethod(
nameof(DiagnosticManager.OnDiagnosticListenerNext),
BindingFlags.Instance | BindingFlags.NonPublic);

if (onDiagnosticListenerNextMethodInfo == null)
{
Log.Warning("Unable to find OnDiagnosticListenerNext method on DiagnosticManager");
return false;
}

// Define OnNext method
var onNextMethod = typeBuilder.DefineMethod("OnNext", methodAttributes, typeof(void), new[] { diagnosticListenerType });
var il = onNextMethod.GetILGenerator();

// Generate: this._manager.OnDiagnosticListenerNext(listener);
il.Emit(OpCodes.Ldarg_0); // Load 'this'
il.Emit(OpCodes.Ldfld, managerField); // Load this._manager
il.Emit(OpCodes.Ldarg_1); // Load the listener parameter
il.EmitCall(OpCodes.Callvirt, onDiagnosticListenerNextMethodInfo, null); // Call the method
il.Emit(OpCodes.Ret); // Return

return true;
}
}
}
Loading
Loading