diff --git a/dotnet/src/dotnetcore/GxClasses/Services/OpenTelemetry/OpenTelemetryService.cs b/dotnet/src/dotnetcore/GxClasses/Services/OpenTelemetry/OpenTelemetryService.cs index 3741c9a79..3dfdf2b00 100644 --- a/dotnet/src/dotnetcore/GxClasses/Services/OpenTelemetry/OpenTelemetryService.cs +++ b/dotnet/src/dotnetcore/GxClasses/Services/OpenTelemetry/OpenTelemetryService.cs @@ -1,4 +1,5 @@ using System; +using System.Text.RegularExpressions; using GxClasses.Helpers; namespace GeneXus.Services.OpenTelemetry @@ -12,9 +13,54 @@ public static class OpenTelemetryService { private static readonly IGXLogger log = GXLoggerFactory.GetLogger(typeof(OpenTelemetryService).FullName); - private static string OPENTELEMETRY_SERVICE = "Observability"; - public static string GX_ACTIVITY_SOURCE_NAME = "GeneXus.Tracing"; - + const string OTEL_RESOURCE_ATTRIBUTES = "OTEL_RESOURCE_ATTRIBUTES"; + const string OTEL_SERVICE_NAME = "OTEL_SERVICE_NAME"; + const string OTEL_SERVICE_VERSION = "OTEL_SERVICE_VERSION"; + const string OPENTELEMETRY_SERVICE = "Observability"; + + public static string GX_ACTIVITY_SOURCE_NAME= GetServiceNameValue(OTEL_RESOURCE_ATTRIBUTES); + public static string GX_ACTIVITY_SOURCE_VERSION= GetServiceVersionValue(OTEL_RESOURCE_ATTRIBUTES); + + private static string GetServiceNameValue(string input) + { + string otelServiceNameEnvVar = Environment.GetEnvironmentVariable(OTEL_SERVICE_NAME); + if (!string.IsNullOrEmpty(otelServiceNameEnvVar)) + return otelServiceNameEnvVar; + + string pattern = @"(?:\b\w+\b=\w+)(?:,(?:\b\w+\b=\w+))*"; + MatchCollection matches = Regex.Matches(input, pattern); + + foreach (Match match in matches) + { + string[] keyValue = match.Value.Split('='); + + if (keyValue[0] == "service.name") + { + return keyValue[1]; + } + } + return "GeneXus.Tracing"; + } + private static string GetServiceVersionValue(string input) + { + string otelServiceNameEnvVar = Environment.GetEnvironmentVariable(OTEL_SERVICE_VERSION); + if (!string.IsNullOrEmpty(otelServiceNameEnvVar)) + return otelServiceNameEnvVar; + + string pattern = @"(?:\b\w+\b=\w+)(?:,(?:\b\w+\b=\w+))*"; + MatchCollection matches = Regex.Matches(input, pattern); + + foreach (Match match in matches) + { + string[] keyValue = match.Value.Split('='); + + if (keyValue[0] == "service.version") + { + return keyValue[1]; + } + } + return string.Empty; + } private static IOpenTelemetryProvider GetOpenTelemetryProvider() { IOpenTelemetryProvider otelProvider = null; @@ -34,7 +80,7 @@ private static IOpenTelemetryProvider GetOpenTelemetryProvider() } catch (Exception e) { - GXLogging.Error(log, "CouldnĀ“t create OpenTelemetry provider.", e.Message, e); + GXLogging.Error(log, "Couldn't create OpenTelemetry provider.", e.Message, e); throw e; } } diff --git a/dotnet/src/dotnetcore/Providers/OpenTelemetry/Diagnostics/GXOtel.Diagnostics/OtelSpan.cs b/dotnet/src/dotnetcore/Providers/OpenTelemetry/Diagnostics/GXOtel.Diagnostics/OtelSpan.cs index 4b35201d4..d05c93aa8 100644 --- a/dotnet/src/dotnetcore/Providers/OpenTelemetry/Diagnostics/GXOtel.Diagnostics/OtelSpan.cs +++ b/dotnet/src/dotnetcore/Providers/OpenTelemetry/Diagnostics/GXOtel.Diagnostics/OtelSpan.cs @@ -1,112 +1,183 @@ using System; using System.Diagnostics; -using GeneXus.Application; +using GeneXus.Attributes; +using OpenTelemetry; using OpenTelemetry.Trace; -using static GeneXus.OpenTelemetry.Diagnostics.OtelTracer; namespace GeneXus.OpenTelemetry.Diagnostics { + [GXApi] public class OtelSpan { private Activity _activity; - public enum SpanStatusCode { Unset, Ok, Error } - public string Id - { get => _activity?.Id; } - public bool IsAllDataRequested + internal OtelSpan(Activity activity) { - get - { - if (_activity != null) - return _activity.IsAllDataRequested; - return false; - } + _activity = activity; } - public bool IsStopped - { get - { - if (_activity != null ) - return _activity.IsStopped; - return false; - } - } + public OtelSpan() + {} - public short Kind - { get => (short)_activity?.Kind; } + public Activity Activity => _activity; - public string ParentId - { get => _activity?.ParentId; } + #region EO Properties + public GXSpanContext SpanContext => _activity == null ? null : new GXSpanContext(_activity.Context); - public string ParentSpanId - { get => _activity?.ParentSpanId.ToString(); } + public GXTraceContext GetContext => _activity == null ? null : new GXTraceContext(_activity.Context); + public string SpanId => _activity?.Id; - public string TraceId - { get => _activity?.TraceId.ToString(); } + public string TraceId => _activity?.TraceId.ToHexString(); - public short Status - { get => (short)_activity?.Status; } - - internal OtelSpan(Activity activity) + #endregion + + #region Methods + public void Stop() { - _activity = activity; + _activity?.Stop(); + } + public void RecordException(string message) + { + _activity?.RecordException(new Exception(message)); } - public OtelSpan() + public void SetStringAttribute(string property, string value) { - _activity = Activity.Current; + _activity?.SetTag(property, value); + } - public void Start() + public void SetLongAttribute(string property, long value) { - _activity?.Start(); + _activity?.SetTag(property, value); + } + public void SetDoubleAttribute(string property, double value) + { + _activity?.SetTag(property, value); - public void Stop() + } + public void SetBooleanAttribute(string property, bool value) { - _activity?.Stop(); + _activity?.SetTag(property, value); + } - public void RecordException(string message) + public GXTraceContext AddBaggage(string property, string value) + { + Baggage.SetBaggage(property, value); + if (_activity != null) + return new GXTraceContext(_activity.Context); + else return null; + } + + public string GetBaggageItem(string property,GXTraceContext gXTraceContext) + { + return Baggage.GetBaggage(property); + } + public void SetStatus(SpanStatusCode spanStatusCode, string message) { - _activity.RecordException(new Exception(message)); + _activity?.SetStatus((ActivityStatusCode)spanStatusCode, message); } + public void SetStatus(SpanStatusCode spanStatusCode) + { + _activity?.SetStatus((ActivityStatusCode)spanStatusCode); + } + + #endregion - public void SetTag(string property, string value) + #region Private Methods + public bool IsRecording => IsSpanRecording(); + + private bool IsSpanRecording() { - _activity.SetTag(property, value); + if (_activity != null) + return _activity.IsAllDataRequested; + else + return false; } - public string GetTagItem(string property) + #endregion + + } + + public class GXTraceContext : GXSpanContext + { + //Dummy class to be compatible with Java. + //.NET does not requiere propagating the context explicitly in most of the cases. + //https://github.com/open-telemetry/opentelemetry-dotnet/blob/main/src/OpenTelemetry.Api/README.md#baggage-api + + public GXTraceContext(ActivityContext activityContext):base(activityContext) { } + public GXTraceContext():base() { } + } + public class GXSpanContext + { + private ActivityContext _activityContext; + public GXSpanContext() { - return _activity.GetTagItem(property).ToString(); + _activityContext = Activity.Current.Context; } - public void AddBaggage(string property, string value) + + public GXSpanContext(ActivityContext activityContext) { - _activity.AddBaggage(property, value); + _activityContext = activityContext; } - public string GetBaggageItem(string property) + public ActivityContext ActivityContext { get { return _activityContext; } } + + public string TraceId => GetTraceId(); + + public string SpanId => GetSpanId(); + + private string GetTraceId() { - return _activity.GetBaggageItem(property).ToString(); + if (_activityContext != null) + { + ActivityTraceId activityTraceId = _activityContext.TraceId; + return activityTraceId.ToHexString(); + } else return null; + } + private string GetSpanId() + { + if (_activityContext != null) + { + ActivitySpanId activitySpanId = _activityContext.SpanId; + return activitySpanId.ToHexString(); + } else { return null; } } - public void SetStatus(SpanStatusCode spanStatusCode, string message) + private GXActivityTraceFlags TraceFlags() { - _activity.SetStatus((ActivityStatusCode)spanStatusCode, message); + if (_activityContext != null) { + ActivityTraceFlags activityTraceFlags = _activityContext.TraceFlags; + return (GXActivityTraceFlags)activityTraceFlags; + } + else { return GXActivityTraceFlags.None;} } - public SpanStatusCode GetStatus() + private string TraceState() { - return (SpanStatusCode)_activity.GetStatus().StatusCode; + if (_activityContext != null) + return _activityContext.TraceState; + else return null; } - //ToDO - //public void AddEvent() - //{ - //} + // + // Summary: + // Specifies flags defined by the W3C standard that are associated with an activity. + internal enum GXActivityTraceFlags + { + // + // Summary: + // The activity has not been marked. + None = 0, + // + // Summary: + // The activity (or more likely its parents) has been marked as useful to record. + Recorded = 1 + } } } diff --git a/dotnet/src/dotnetcore/Providers/OpenTelemetry/Diagnostics/GXOtel.Diagnostics/OtelTracer.cs b/dotnet/src/dotnetcore/Providers/OpenTelemetry/Diagnostics/GXOtel.Diagnostics/OtelTracer.cs index 8ce722bef..812b1ae28 100644 --- a/dotnet/src/dotnetcore/Providers/OpenTelemetry/Diagnostics/GXOtel.Diagnostics/OtelTracer.cs +++ b/dotnet/src/dotnetcore/Providers/OpenTelemetry/Diagnostics/GXOtel.Diagnostics/OtelTracer.cs @@ -1,35 +1,103 @@ +using System; +using System.Collections.Generic; using System.Diagnostics; -using GeneXus.Application; +using System.Linq; using GeneXus.Attributes; +using GeneXus.Services.OpenTelemetry; namespace GeneXus.OpenTelemetry.Diagnostics { [GXApi] public class OtelTracer { + internal static ActivitySource activitySource; + internal static ActivitySource ActivitySource + { + get + { + if (activitySource == null) + { + activitySource = string.IsNullOrEmpty(OpenTelemetryService.GX_ACTIVITY_SOURCE_VERSION) ? new(OpenTelemetryService.GX_ACTIVITY_SOURCE_NAME) : new(OpenTelemetryService.GX_ACTIVITY_SOURCE_NAME, OpenTelemetryService.GX_ACTIVITY_SOURCE_VERSION); + } + return activitySource; + } + } public enum SpanType { - Client, - Consumer, Internal, + Server, + Client, Producer, - Server + Consumer + } + + public OtelSpan CreateSpan(string name) + { + //Returns null when the are no listeners. + Activity activity = ActivitySource.StartActivity(name); + if (activity != null) + return new OtelSpan(activity); + return null; } public OtelSpan CreateSpan(string name, SpanType kind) { - Activity activity = GXBaseObject.ActivitySource.StartActivity(name, (ActivityKind)kind); + Activity activity = ActivitySource.StartActivity(name, (ActivityKind)kind); + if (activity != null) + return new OtelSpan(activity); + else + return null; + } + + public OtelSpan CreateSpan(string name, GXTraceContext gxTraceContext, SpanType spanType) + { + Activity activity = ActivitySource.StartActivity(name, + kind: (ActivityKind)spanType, + parentContext: gxTraceContext.ActivityContext); + return new OtelSpan(activity); + + } + + public OtelSpan CreateSpan(string name, GXTraceContext gxTraceContext, SpanType spanType, IList gxSpanContexts) + { + //https://learn.microsoft.com/en-us/dotnet/core/diagnostics/distributed-tracing-instrumentation-walkthroughs#optional-links + List contexts = new List(); + + foreach (GXSpanContext gxSpanContext in gxSpanContexts) + { + ActivityContext context = gxSpanContext.ActivityContext; + contexts.Add(context); + } + + Activity activity = ActivitySource.StartActivity(name, + kind: (ActivityKind)spanType, + parentContext: gxTraceContext.ActivityContext, + links: contexts.Select(ctx => new ActivityLink(ctx))); return new OtelSpan(activity); } - public OtelSpan GetCurrent() + public OtelSpan CreateSpan(string name, GXTraceContext gxTraceContext, SpanType spanType, IList gxSpanContexts, DateTime dateTime) { - return new OtelSpan(Activity.Current); + //https://learn.microsoft.com/en-us/dotnet/core/diagnostics/distributed-tracing-instrumentation-walkthroughs#optional-links + List contexts = new List(); + + foreach (GXSpanContext gxSpanContext in gxSpanContexts) + { + ActivityContext context = gxSpanContext.ActivityContext; + contexts.Add(context); + } + + Activity activity = ActivitySource.StartActivity(name, + kind: (ActivityKind)spanType, + parentContext: gxTraceContext.ActivityContext, + links: contexts.Select(ctx => new ActivityLink(ctx)), + startTime:dateTime); + return new OtelSpan(activity); } - public bool HasListeners() + public static OtelSpan GetCurrent() { - return GXBaseObject.ActivitySource.HasListeners(); + return new OtelSpan(Activity.Current); } } }