Skip to content
Open
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
58 changes: 57 additions & 1 deletion src/OpenTelemetry/Internal/SelfDiagnosticsConfigParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@

namespace OpenTelemetry.Internal;

internal sealed class SelfDiagnosticsConfigParser
/// <summary>
/// Reads diagnostic configuration from OTEL_DIAGNOSTICS.json file.
/// </summary>
internal sealed partial class SelfDiagnosticsConfigParser
{
public const string ConfigFileName = "OTEL_DIAGNOSTICS.json";
private const int FileSizeLowerLimit = 1024; // Lower limit for log file size in KB: 1MB
Expand All @@ -19,6 +22,7 @@ internal sealed class SelfDiagnosticsConfigParser
/// </summary>
private const int ConfigBufferSize = 4 * 1024;

#if !NET7_0_OR_GREATER
private static readonly Regex LogDirectoryRegex = new(
@"""LogDirectory""\s*:\s*""(?<LogDirectory>.*?)""", RegexOptions.IgnoreCase | RegexOptions.Compiled);

Expand All @@ -30,6 +34,7 @@ internal sealed class SelfDiagnosticsConfigParser

private static readonly Regex FormatMessageRegex = new(
@"""FormatMessage""\s*:\s*(?:""(?<FormatMessage>.*?)""|(?<FormatMessage>true|false))", RegexOptions.IgnoreCase | RegexOptions.Compiled);
#endif

// This class is called in SelfDiagnosticsConfigRefresher.UpdateMemoryMappedFileFromConfiguration
// in both main thread and the worker thread.
Expand Down Expand Up @@ -87,6 +92,48 @@ public bool TryGetConfiguration(

string configJson = Encoding.UTF8.GetString(buffer, 0, totalBytesRead);

#if NET7_0_OR_GREATER
Dictionary<string, string> configDict = new Dictionary<string, string>();
for (Match m = JsonRegex().Match(configJson); m.Success; m = m.NextMatch())
{
switch (m.Groups[1].Value)
{
case "LogDirectory":
logDirectory = m.Groups[2].Value;
break;
case "FileSize":
if (int.TryParse(m.Groups[2].Value, out fileSizeInKB))
{
if (fileSizeInKB < FileSizeLowerLimit)
{
fileSizeInKB = FileSizeLowerLimit;
}

if (fileSizeInKB > FileSizeUpperLimit)
{
fileSizeInKB = FileSizeUpperLimit;
}
}

break;
case "LogLevel":
if (!Enum.TryParse(m.Groups[2].Value, true, out logLevel))
{
return false;
}

break;
case "FormatMessage":
_ = bool.TryParse(m.Groups[2].Value, out formatMessage);
break;
default:
// Ignore unknown fields
break;
}
}

return !string.IsNullOrEmpty(logDirectory) && fileSizeInKB > 0;
#else
if (!TryParseLogDirectory(configJson, out logDirectory))
{
return false;
Expand Down Expand Up @@ -116,6 +163,7 @@ public bool TryGetConfiguration(
_ = TryParseFormatMessage(configJson, out formatMessage);

return Enum.TryParse(logLevelString, out logLevel);
#endif
}
catch (Exception)
{
Expand All @@ -124,6 +172,13 @@ public bool TryGetConfiguration(
}
}

/// <summary>
/// Regex for parsing JSON into "field":"value" pairs. Will work on the flat JSONs.
/// </summary>
#if NET7_0_OR_GREATER
[GeneratedRegex(@"\""(\w+)\""[\s\r\n]*:[\s\r\n]*\""?([^},]*?)[""},]", RegexOptions.CultureInvariant)]
internal static partial Regex JsonRegex();
#else
internal static bool TryParseLogDirectory(
string configJson,
out string logDirectory)
Expand Down Expand Up @@ -162,4 +217,5 @@ internal static bool TryParseFormatMessage(string configJson, out bool formatMes

return true;
}
#endif
}
67 changes: 56 additions & 11 deletions src/OpenTelemetry/Internal/WildcardHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
using System.Globalization;

namespace OpenTelemetry;

Expand All @@ -25,18 +25,63 @@ public static bool ContainsWildcard(
#endif
}

public static Regex GetWildcardRegex(IEnumerable<string> patterns)
public static bool MatchAny(IEnumerable<string> templates, string s)
{
Debug.Assert(patterns?.Any() == true, "patterns was null or empty");
foreach (string wildcard in templates)
{
if (wildcard.WildcardMatch(s, 0, 0, true))
{
return true;
}
}

var convertedPattern = string.Join(
"|",
#if NET || NETSTANDARD2_1_OR_GREATER
from p in patterns select "(?:" + Regex.Escape(p).Replace("\\*", ".*", StringComparison.Ordinal).Replace("\\?", ".", StringComparison.Ordinal) + ')');
#else
from p in patterns select "(?:" + Regex.Escape(p).Replace("\\*", ".*").Replace("\\?", ".") + ')');
#endif
return false;
}

// Taken from https://github.com/picrap/WildcardMatch/blob/master/WildcardMatch/StringExtensions.cs
public static bool WildcardMatch(this string wildcard, string s, int wildcardIndex, int sIndex, bool ignoreCase)
{
for (; ;)
{
// in the wildcard end, if we are at tested string end, then strings match
if (wildcardIndex == wildcard.Length)
{
return sIndex == s.Length;
}

var c = wildcard[wildcardIndex];
switch (c)
{
// always a match
case '?':
break;
case '*':
// if this is the last wildcard char, then we have a match, whatever the tested string is
if (wildcardIndex == wildcard.Length - 1)
{
return true;
}

// test if a match follows
return Enumerable.Range(sIndex, s.Length - sIndex).Any(i => WildcardMatch(wildcard, s, wildcardIndex + 1, i, ignoreCase));
default:
var cc = ignoreCase ? char.ToLower(c, CultureInfo.InvariantCulture) : c;
if (s.Length == sIndex)
{
return false;
}

return new Regex("^(?:" + convertedPattern + ")$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
var sc = ignoreCase ? char.ToLower(s[sIndex], CultureInfo.InvariantCulture) : s[sIndex];
if (cc != sc)
{
return false;
}

break;
}

wildcardIndex++;
sIndex++;
}
}
}
35 changes: 25 additions & 10 deletions src/OpenTelemetry/Metrics/Builder/MeterProviderBuilderSdk.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,6 @@ public MeterProviderBuilderSdk(IServiceProvider serviceProvider)
this.serviceProvider = serviceProvider;
}

// Note: We don't use static readonly here because some customers
// replace this using reflection which is not allowed on initonly static
// fields. See: https://github.com/dotnet/runtime/issues/11571.
// Customers: This is not guaranteed to work forever. We may change this
// mechanism in the future do this at your own risk.
public static Regex InstrumentNameRegex { get; set; } = new(
@"^[a-z][a-z0-9-._/]{0,254}$", RegexOptions.IgnoreCase | RegexOptions.Compiled);

public List<InstrumentationRegistration> Instrumentation { get; } = new();

public ResourceBuilder? ResourceBuilder { get; private set; }
Expand Down Expand Up @@ -66,7 +58,7 @@ public static bool IsValidInstrumentName(string instrumentName)
return false;
}

return InstrumentNameRegex.IsMatch(instrumentName);
return IsValidName(instrumentName);
}

/// <summary>
Expand All @@ -83,7 +75,7 @@ public static bool IsValidViewName(string customViewName)
return true;
}

return InstrumentNameRegex.IsMatch(customViewName);
return IsValidName(customViewName);
}

public void RegisterProvider(MeterProviderSdk meterProvider)
Expand Down Expand Up @@ -215,6 +207,29 @@ public MeterProviderBuilder ConfigureServices(Action<IServiceCollection> configu
MeterProviderBuilder IDeferredMeterProviderBuilder.Configure(Action<IServiceProvider, MeterProviderBuilder> configure)
=> this.ConfigureBuilder(configure);

private static bool IsValidName(string name)
{
if (name.Length > 254)
{
return false;
}

for (int i = 0; i < name.Length; i++)
{
if ((name[i] >= '-' && name[i] <= '9' && i > 0) ||
(name[i] >= 'A' && name[i] <= 'Z') ||
(name[i] >= 'a' && name[i] <= 'z') ||
(name[i] == '_' && i > 0))
{
continue;
}

return false;
}

return true;
}

internal readonly struct InstrumentationRegistration
{
public readonly string Name;
Expand Down
3 changes: 1 addition & 2 deletions src/OpenTelemetry/Metrics/MeterProviderSdk.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,7 @@ internal MeterProviderSdk(
// Setup Listener
if (state.MeterSources.Exists(WildcardHelper.ContainsWildcard))
{
var regex = WildcardHelper.GetWildcardRegex(state.MeterSources);
this.shouldListenTo = instrument => regex.IsMatch(instrument.Meter.Name);
this.shouldListenTo = instrument => WildcardHelper.MatchAny(state.MeterSources, instrument.Meter.Name);
}
else if (state.MeterSources.Count > 0)
{
Expand Down
30 changes: 4 additions & 26 deletions src/OpenTelemetry/Trace/TracerProviderSdk.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,6 @@ internal TracerProviderSdk(

this.supportLegacyActivity = state.LegacyActivityOperationNames.Count > 0;

Regex? legacyActivityWildcardModeRegex = null;
foreach (var legacyName in state.LegacyActivityOperationNames)
{
if (WildcardHelper.ContainsWildcard(legacyName))
{
legacyActivityWildcardModeRegex = WildcardHelper.GetWildcardRegex(state.LegacyActivityOperationNames);
break;
}
}

// Note: Linq OrderBy performs a stable sort, which is a requirement here
IEnumerable<BaseProcessor<Activity>> processors = state.Processors.OrderBy(p => p.PipelineWeight);

Expand Down Expand Up @@ -116,23 +106,13 @@ internal TracerProviderSdk(

if (this.supportLegacyActivity)
{
Func<Activity, bool>? legacyActivityPredicate = null;
if (legacyActivityWildcardModeRegex != null)
{
legacyActivityPredicate = activity => legacyActivityWildcardModeRegex.IsMatch(activity.OperationName);
}
else
{
legacyActivityPredicate = activity => state.LegacyActivityOperationNames.Contains(activity.OperationName);
}

activityListener.ActivityStarted = activity =>
{
OpenTelemetrySdkEventSource.Log.ActivityStarted(activity);

if (string.IsNullOrEmpty(activity.Source.Name))
{
if (legacyActivityPredicate(activity))
if (WildcardHelper.MatchAny(state.LegacyActivityOperationNames, activity.OperationName))
{
// Legacy activity matches the user configured list.
// Call sampler for the legacy activity
Expand Down Expand Up @@ -168,7 +148,7 @@ internal TracerProviderSdk(
{
OpenTelemetrySdkEventSource.Log.ActivityStopped(activity);

if (string.IsNullOrEmpty(activity.Source.Name) && !legacyActivityPredicate(activity))
if (string.IsNullOrEmpty(activity.Source.Name) && !WildcardHelper.MatchAny(state.LegacyActivityOperationNames, activity.OperationName))
{
// Legacy activity doesn't match the user configured list. No need to proceed further.
return;
Expand Down Expand Up @@ -257,14 +237,12 @@ internal TracerProviderSdk(
// Validation of source name is already done in builder.
if (state.Sources.Any(s => WildcardHelper.ContainsWildcard(s)))
{
var regex = WildcardHelper.GetWildcardRegex(state.Sources);

// Function which takes ActivitySource and returns true/false to indicate if it should be subscribed to
// or not.
activityListener.ShouldListenTo = activitySource =>
this.supportLegacyActivity ?
string.IsNullOrEmpty(activitySource.Name) || regex.IsMatch(activitySource.Name) :
regex.IsMatch(activitySource.Name);
string.IsNullOrEmpty(activitySource.Name) || WildcardHelper.MatchAny(state.Sources, activitySource.Name) :
WildcardHelper.MatchAny(state.Sources, activitySource.Name);
}
else
{
Expand Down