From 0add668b6821a7c5fdc2116c31ee2f2f2db3cba9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20P=C3=B6lz?=
<38893694+Flash0ver@users.noreply.github.com>
Date: Wed, 30 Apr 2025 13:09:47 +0200
Subject: [PATCH 001/101] feat(logs): initial experiment
---
.../Sentry.Samples.Console.Basic/Program.cs | 12 +++
.../Experimental/SentryExperimentalSdk.cs | 20 ++++
.../Experimental/SentryHubExtensions.cs | 15 +++
src/Sentry/Experimental/SentryLog.cs | 102 ++++++++++++++++++
src/Sentry/Experimental/SentrySeverity.cs | 34 ++++++
...tics.CodeAnalysis.ExperimentalAttribute.cs | 29 +++++
src/Sentry/Infrastructure/DiagnosticId.cs | 2 +
src/Sentry/Protocol/Envelopes/Envelope.cs | 14 +++
src/Sentry/Protocol/Envelopes/EnvelopeItem.cs | 16 +++
9 files changed, 244 insertions(+)
create mode 100644 src/Sentry/Experimental/SentryExperimentalSdk.cs
create mode 100644 src/Sentry/Experimental/SentryHubExtensions.cs
create mode 100644 src/Sentry/Experimental/SentryLog.cs
create mode 100644 src/Sentry/Experimental/SentrySeverity.cs
create mode 100644 src/Sentry/Experimental/System.Diagnostics.CodeAnalysis.ExperimentalAttribute.cs
diff --git a/samples/Sentry.Samples.Console.Basic/Program.cs b/samples/Sentry.Samples.Console.Basic/Program.cs
index be18640323..4dd36556a2 100644
--- a/samples/Sentry.Samples.Console.Basic/Program.cs
+++ b/samples/Sentry.Samples.Console.Basic/Program.cs
@@ -9,6 +9,7 @@
*/
using System.Net.Http;
+using Sentry.Experimental;
using static System.Console;
// Initialize the Sentry SDK. (It is not necessary to dispose it.)
@@ -35,6 +36,16 @@
options.TracesSampleRate = 1.0;
});
+#pragma warning disable SENTRY0002
+SentryExperimentalSdk.CaptureLog(SentrySeverity.Trace, "Hello, World!");
+SentryExperimentalSdk.CaptureLog(SentrySeverity.Debug, "Hello, .NET!");
+SentryExperimentalSdk.CaptureLog(SentrySeverity.Info, "Information");
+SentryExperimentalSdk.CaptureLog(SentrySeverity.Warn, "Warning with one {0}", "parameter");
+SentryExperimentalSdk.CaptureLog(SentrySeverity.Error, "Error with {0} {1}", 2, "parameters");
+SentryExperimentalSdk.CaptureLog(SentrySeverity.Fatal, "Fatal {0} and {1}", true, false);
+#pragma warning restore SENTRY0002
+
+/*
// This starts a new transaction and attaches it to the scope.
var transaction = SentrySdk.StartTransaction("Program Main", "function");
SentrySdk.ConfigureScope(scope => scope.Transaction = transaction);
@@ -96,3 +107,4 @@ async Task ThirdFunction()
span.Finish();
}
}
+*/
diff --git a/src/Sentry/Experimental/SentryExperimentalSdk.cs b/src/Sentry/Experimental/SentryExperimentalSdk.cs
new file mode 100644
index 0000000000..a08bb073d8
--- /dev/null
+++ b/src/Sentry/Experimental/SentryExperimentalSdk.cs
@@ -0,0 +1,20 @@
+using Sentry.Infrastructure;
+
+namespace Sentry.Experimental;
+
+///
+/// Experimental Sentry SDK entrypoint.
+///
+public static class SentryExperimentalSdk
+{
+ ///
+ /// See: https://github.com/getsentry/sentry-dotnet/issues/4132
+ ///
+ [Experimental(DiagnosticId.ExperimentalSentryLogs, UrlFormat = "https://github.com/getsentry/sentry-dotnet/issues/4132")]
+ public static void CaptureLog(SentrySeverity level, string template, params object[]? parameters)
+ {
+ string message = String.Format(template, parameters ?? []);
+ SentryLog log = new(level, message);
+ _ = SentrySdk.CurrentHub.CaptureLog(log);
+ }
+}
diff --git a/src/Sentry/Experimental/SentryHubExtensions.cs b/src/Sentry/Experimental/SentryHubExtensions.cs
new file mode 100644
index 0000000000..fd89211250
--- /dev/null
+++ b/src/Sentry/Experimental/SentryHubExtensions.cs
@@ -0,0 +1,15 @@
+using Sentry.Infrastructure;
+using Sentry.Protocol.Envelopes;
+
+namespace Sentry.Experimental;
+
+internal static class SentryHubExtensions
+{
+ [Experimental(DiagnosticId.ExperimentalSentryLogs)]
+ internal static int CaptureLog(this IHub hub, SentryLog log)
+ {
+ _ = hub.CaptureEnvelope(Envelope.FromLog(log));
+
+ return default;
+ }
+}
diff --git a/src/Sentry/Experimental/SentryLog.cs b/src/Sentry/Experimental/SentryLog.cs
new file mode 100644
index 0000000000..f46dacf397
--- /dev/null
+++ b/src/Sentry/Experimental/SentryLog.cs
@@ -0,0 +1,102 @@
+using Sentry.Extensibility;
+using Sentry.Infrastructure;
+using Sentry.Internal.Extensions;
+
+namespace Sentry.Experimental;
+
+[Experimental(DiagnosticId.ExperimentalSentryLogs)]
+internal sealed class SentryLog : ISentryJsonSerializable
+{
+ [SetsRequiredMembers]
+ public SentryLog(SentrySeverity level, string message, object[]? parameters = null)
+ {
+ Timestamp = DateTimeOffset.UtcNow;
+ TraceId = SentryId.Empty;
+ Level = level;
+ Message = message;
+ Parameters = parameters;
+ }
+
+ public required DateTimeOffset Timestamp { get; init; }
+
+ public required SentryId TraceId { get; init; }
+
+ public required SentrySeverity Level { get; init; }
+
+ public required string Message { get; init; }
+
+ public Dictionary? Attributes { get; private set; }
+
+ public string? Template { get; init; }
+
+ public object[]? Parameters { get; init; }
+
+ public int SeverityNumber { get; init; } = -1;
+
+ public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
+ {
+ Attributes = new Dictionary
+ {
+ //{ "sentry.environment", new ValueTypePair("production", "string")},
+ //{ "sentry.release", new ValueTypePair("1.0.0", "string")},
+ //{ "sentry.trace.parent_span_id", new ValueTypePair("b0e6f15b45c36b12", "string")},
+ };
+ if (Template is not null)
+ {
+ Attributes["sentry.message.template"] = new ValueTypePair("User %s has logged in!", "string");
+ }
+
+ if (Parameters is not null)
+ {
+ for (var index = 0; index < Parameters.Length; index++)
+ {
+ Attributes[$"sentry.message.parameters.{index}"] = new ValueTypePair(Parameters[index], "string");
+ }
+ }
+
+ writer.WriteStartObject();
+
+ writer.WriteStartArray("items");
+
+ writer.WriteStartObject();
+
+ writer.WriteNumber("timestamp", Timestamp.ToUnixTimeSeconds());
+ writer.WriteString("trace_id", TraceId);
+ writer.WriteString("level", Level.ToLogString());
+ writer.WriteString("body", Message);
+ writer.WriteDictionaryIfNotEmpty("attributes", Attributes, logger);
+
+ if (SeverityNumber != -1)
+ {
+ writer.WriteNumber("severity_number", SeverityNumber);
+ }
+
+ writer.WriteEndObject();
+
+ writer.WriteEndArray();
+
+ writer.WriteEndObject();
+ }
+}
+
+internal readonly struct ValueTypePair : ISentryJsonSerializable
+{
+ public ValueTypePair(object value, string type)
+ {
+ Value = value.ToString()!;
+ Type = type;
+ }
+
+ public string Value { get; }
+ public string Type { get; }
+
+ public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
+ {
+ writer.WriteStartObject();
+
+ writer.WriteString("value", Value);
+ writer.WriteString("type", Type);
+
+ writer.WriteEndObject();
+ }
+}
diff --git a/src/Sentry/Experimental/SentrySeverity.cs b/src/Sentry/Experimental/SentrySeverity.cs
new file mode 100644
index 0000000000..92bcdd5c46
--- /dev/null
+++ b/src/Sentry/Experimental/SentrySeverity.cs
@@ -0,0 +1,34 @@
+using Sentry.Infrastructure;
+
+namespace Sentry.Experimental;
+
+#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
+[Experimental(DiagnosticId.ExperimentalSentryLogs)]
+public enum SentrySeverity : short
+{
+ Trace,
+ Debug,
+ Info,
+ Warn,
+ Error,
+ Fatal,
+}
+#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
+
+[Experimental(DiagnosticId.ExperimentalSentryLogs)]
+internal static class SentrySeverityExtensions
+{
+ public static string ToLogString(this SentrySeverity severity)
+ {
+ return severity switch
+ {
+ SentrySeverity.Trace => "trace",
+ SentrySeverity.Debug => "debug",
+ SentrySeverity.Info => "info",
+ SentrySeverity.Warn => "warn",
+ SentrySeverity.Error => "error",
+ SentrySeverity.Fatal => "fatal",
+ _ => throw new ArgumentOutOfRangeException(nameof(severity), severity, null),
+ };
+ }
+}
diff --git a/src/Sentry/Experimental/System.Diagnostics.CodeAnalysis.ExperimentalAttribute.cs b/src/Sentry/Experimental/System.Diagnostics.CodeAnalysis.ExperimentalAttribute.cs
new file mode 100644
index 0000000000..b173d83323
--- /dev/null
+++ b/src/Sentry/Experimental/System.Diagnostics.CodeAnalysis.ExperimentalAttribute.cs
@@ -0,0 +1,29 @@
+#if !NET8_0_OR_GREATER
+// ReSharper disable CheckNamespace
+// ReSharper disable ConvertToPrimaryConstructor
+namespace System.Diagnostics.CodeAnalysis;
+
+[AttributeUsage(AttributeTargets.Assembly |
+ AttributeTargets.Module |
+ AttributeTargets.Class |
+ AttributeTargets.Struct |
+ AttributeTargets.Enum |
+ AttributeTargets.Constructor |
+ AttributeTargets.Method |
+ AttributeTargets.Property |
+ AttributeTargets.Field |
+ AttributeTargets.Event |
+ AttributeTargets.Interface |
+ AttributeTargets.Delegate, Inherited = false)]
+internal sealed class ExperimentalAttribute : Attribute
+{
+ public ExperimentalAttribute(string diagnosticId)
+ {
+ DiagnosticId = diagnosticId;
+ }
+
+ public string DiagnosticId { get; }
+
+ public string? UrlFormat { get; set; }
+}
+#endif
diff --git a/src/Sentry/Infrastructure/DiagnosticId.cs b/src/Sentry/Infrastructure/DiagnosticId.cs
index 92703ddc87..ebd17b51d2 100644
--- a/src/Sentry/Infrastructure/DiagnosticId.cs
+++ b/src/Sentry/Infrastructure/DiagnosticId.cs
@@ -8,4 +8,6 @@ internal static class DiagnosticId
///
internal const string ExperimentalFeature = "SENTRY0001";
#endif
+
+ internal const string ExperimentalSentryLogs = "SENTRY0002";
}
diff --git a/src/Sentry/Protocol/Envelopes/Envelope.cs b/src/Sentry/Protocol/Envelopes/Envelope.cs
index b62dc82c98..0fb07b47d2 100644
--- a/src/Sentry/Protocol/Envelopes/Envelope.cs
+++ b/src/Sentry/Protocol/Envelopes/Envelope.cs
@@ -1,3 +1,4 @@
+using Sentry.Experimental;
using Sentry.Extensibility;
using Sentry.Infrastructure;
using Sentry.Internal;
@@ -445,6 +446,19 @@ internal static Envelope FromClientReport(ClientReport clientReport)
return new Envelope(header, items);
}
+ [Experimental(DiagnosticId.ExperimentalSentryLogs)]
+ internal static Envelope FromLog(SentryLog log)
+ {
+ var header = DefaultHeader;
+
+ var items = new[]
+ {
+ EnvelopeItem.FromLog(log)
+ };
+
+ return new Envelope(header, items);
+ }
+
private static async Task> DeserializeHeaderAsync(
Stream stream,
CancellationToken cancellationToken = default)
diff --git a/src/Sentry/Protocol/Envelopes/EnvelopeItem.cs b/src/Sentry/Protocol/Envelopes/EnvelopeItem.cs
index 7c721db581..a73f9e4ab8 100644
--- a/src/Sentry/Protocol/Envelopes/EnvelopeItem.cs
+++ b/src/Sentry/Protocol/Envelopes/EnvelopeItem.cs
@@ -1,4 +1,6 @@
+using Sentry.Experimental;
using Sentry.Extensibility;
+using Sentry.Infrastructure;
using Sentry.Internal;
using Sentry.Internal.Extensions;
using Sentry.Protocol.Metrics;
@@ -24,6 +26,7 @@ public sealed class EnvelopeItem : ISerializable, IDisposable
internal const string TypeValueProfile = "profile";
internal const string TypeValueMetric = "statsd";
internal const string TypeValueCodeLocations = "metric_meta";
+ internal const string TypeValueLog = "log";
private const string LengthKey = "length";
private const string FileNameKey = "filename";
@@ -370,6 +373,19 @@ internal static EnvelopeItem FromClientReport(ClientReport report)
return new EnvelopeItem(header, new JsonSerializable(report));
}
+ [Experimental(DiagnosticId.ExperimentalSentryLogs)]
+ internal static EnvelopeItem FromLog(SentryLog log)
+ {
+ var header = new Dictionary(3, StringComparer.Ordinal)
+ {
+ [TypeKey] = TypeValueLog,
+ ["item_count"] = 1,
+ ["content_type"] = "application/vnd.sentry.items.log+json",
+ };
+
+ return new EnvelopeItem(header, new JsonSerializable(log));
+ }
+
private static async Task> DeserializeHeaderAsync(
Stream stream,
CancellationToken cancellationToken = default)
From db7d558081582ebf2802978b60de6eba6be29a9d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20P=C3=B6lz?=
<38893694+Flash0ver@users.noreply.github.com>
Date: Wed, 30 Apr 2025 18:06:38 +0200
Subject: [PATCH 002/101] feat(logs): basic Logger Module API shape
---
.../Sentry.Samples.Console.Basic/Program.cs | 14 +--
.../Experimental/SentryExperimentalSdk.cs | 20 ----
.../Experimental/SentryHubExtensions.cs | 15 ---
src/Sentry/Experimental/SentryLog.cs | 99 ++++++++++++++-----
src/Sentry/Experimental/SentrySeverity.cs | 46 ++++++++-
...System.Diagnostics.UnreachableException.cs | 22 +++++
src/Sentry/Infrastructure/DiagnosticId.cs | 7 ++
.../Internal/Extensions/JsonExtensions.cs | 11 +++
src/Sentry/SentryLogger.cs | 68 +++++++++++++
src/Sentry/SentrySdk.cs | 9 ++
.../SqlListenerTests.verify.cs | 2 +-
11 files changed, 245 insertions(+), 68 deletions(-)
delete mode 100644 src/Sentry/Experimental/SentryExperimentalSdk.cs
delete mode 100644 src/Sentry/Experimental/SentryHubExtensions.cs
create mode 100644 src/Sentry/Experimental/System.Diagnostics.UnreachableException.cs
create mode 100644 src/Sentry/SentryLogger.cs
diff --git a/samples/Sentry.Samples.Console.Basic/Program.cs b/samples/Sentry.Samples.Console.Basic/Program.cs
index 4dd36556a2..7fab354823 100644
--- a/samples/Sentry.Samples.Console.Basic/Program.cs
+++ b/samples/Sentry.Samples.Console.Basic/Program.cs
@@ -37,14 +37,16 @@
});
#pragma warning disable SENTRY0002
-SentryExperimentalSdk.CaptureLog(SentrySeverity.Trace, "Hello, World!");
-SentryExperimentalSdk.CaptureLog(SentrySeverity.Debug, "Hello, .NET!");
-SentryExperimentalSdk.CaptureLog(SentrySeverity.Info, "Information");
-SentryExperimentalSdk.CaptureLog(SentrySeverity.Warn, "Warning with one {0}", "parameter");
-SentryExperimentalSdk.CaptureLog(SentrySeverity.Error, "Error with {0} {1}", 2, "parameters");
-SentryExperimentalSdk.CaptureLog(SentrySeverity.Fatal, "Fatal {0} and {1}", true, false);
+SentrySdk.Logger.Trace("Hello, World!", null, log => log.SetAttribute("trace", "trace"));
+SentrySdk.Logger.Debug("Hello, .NET!", null, log => log.SetAttribute("trace", "trace"));
+SentrySdk.Logger.Info("Information", null, log => log.SetAttribute("trace", "trace"));
+SentrySdk.Logger.Warn("Warning with one {0}", ["parameter"], log => log.SetAttribute("trace", "trace"));
+SentrySdk.Logger.Error("Error with {0} {1}", [2, "parameters"], log => log.SetAttribute("trace", "trace"));
+SentrySdk.Logger.Fatal("Fatal {0} and {1}", [true, false], log => log.SetAttribute("trace", "trace"));
#pragma warning restore SENTRY0002
+await Task.Delay(5_000);
+
/*
// This starts a new transaction and attaches it to the scope.
var transaction = SentrySdk.StartTransaction("Program Main", "function");
diff --git a/src/Sentry/Experimental/SentryExperimentalSdk.cs b/src/Sentry/Experimental/SentryExperimentalSdk.cs
deleted file mode 100644
index a08bb073d8..0000000000
--- a/src/Sentry/Experimental/SentryExperimentalSdk.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using Sentry.Infrastructure;
-
-namespace Sentry.Experimental;
-
-///
-/// Experimental Sentry SDK entrypoint.
-///
-public static class SentryExperimentalSdk
-{
- ///
- /// See: https://github.com/getsentry/sentry-dotnet/issues/4132
- ///
- [Experimental(DiagnosticId.ExperimentalSentryLogs, UrlFormat = "https://github.com/getsentry/sentry-dotnet/issues/4132")]
- public static void CaptureLog(SentrySeverity level, string template, params object[]? parameters)
- {
- string message = String.Format(template, parameters ?? []);
- SentryLog log = new(level, message);
- _ = SentrySdk.CurrentHub.CaptureLog(log);
- }
-}
diff --git a/src/Sentry/Experimental/SentryHubExtensions.cs b/src/Sentry/Experimental/SentryHubExtensions.cs
deleted file mode 100644
index fd89211250..0000000000
--- a/src/Sentry/Experimental/SentryHubExtensions.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using Sentry.Infrastructure;
-using Sentry.Protocol.Envelopes;
-
-namespace Sentry.Experimental;
-
-internal static class SentryHubExtensions
-{
- [Experimental(DiagnosticId.ExperimentalSentryLogs)]
- internal static int CaptureLog(this IHub hub, SentryLog log)
- {
- _ = hub.CaptureEnvelope(Envelope.FromLog(log));
-
- return default;
- }
-}
diff --git a/src/Sentry/Experimental/SentryLog.cs b/src/Sentry/Experimental/SentryLog.cs
index f46dacf397..fde5a3106f 100644
--- a/src/Sentry/Experimental/SentryLog.cs
+++ b/src/Sentry/Experimental/SentryLog.cs
@@ -2,13 +2,18 @@
using Sentry.Infrastructure;
using Sentry.Internal.Extensions;
+#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
+
namespace Sentry.Experimental;
[Experimental(DiagnosticId.ExperimentalSentryLogs)]
-internal sealed class SentryLog : ISentryJsonSerializable
+public sealed class SentryLog : ISentryJsonSerializable
{
+ private Dictionary? _attributes;
+ private int _severityNumber = -1;
+
[SetsRequiredMembers]
- public SentryLog(SentrySeverity level, string message, object[]? parameters = null)
+ internal SentryLog(SentrySeverity level, string message, object[]? parameters = null)
{
Timestamp = DateTimeOffset.UtcNow;
TraceId = SentryId.Empty;
@@ -21,50 +26,99 @@ public SentryLog(SentrySeverity level, string message, object[]? parameters = nu
public required SentryId TraceId { get; init; }
- public required SentrySeverity Level { get; init; }
+ public SentrySeverity Level
+ {
+ get => SentrySeverityExtensions.FromSeverityNumber(_severityNumber);
+ set => _severityNumber = SentrySeverityExtensions.ToSeverityNumber(value);
+ }
public required string Message { get; init; }
- public Dictionary? Attributes { get; private set; }
+ //public Dictionary? Attributes { get { return _attributes; } }
public string? Template { get; init; }
public object[]? Parameters { get; init; }
- public int SeverityNumber { get; init; } = -1;
+ public required int SeverityNumber
+ {
+ get => _severityNumber;
+ set
+ {
+ //
+ SentrySeverityExtensions.ThrowIfOutOfRange(value);
+ _severityNumber = value;
+ }
+ }
+
+ public void SetAttribute(string key, string value)
+ {
+ _attributes ??= new Dictionary();
+ _attributes[key] = new ValueTypePair(value, "string");
+ }
+
+ public void SetAttribute(string key, bool value)
+ {
+ _attributes ??= new Dictionary();
+ _attributes[key] = new ValueTypePair(value, "boolean");
+ }
+
+ public void SetAttribute(string key, int value)
+ {
+ _attributes ??= new Dictionary();
+ _attributes[key] = new ValueTypePair(value, "integer");
+ }
+
+ public void SetAttribute(string key, double value)
+ {
+ _attributes ??= new Dictionary();
+ _attributes[key] = new ValueTypePair(value, "double");
+ }
public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
{
- Attributes = new Dictionary
+ _attributes = new Dictionary
{
- //{ "sentry.environment", new ValueTypePair("production", "string")},
- //{ "sentry.release", new ValueTypePair("1.0.0", "string")},
- //{ "sentry.trace.parent_span_id", new ValueTypePair("b0e6f15b45c36b12", "string")},
+ { "sentry.environment", new ValueTypePair("production", "string")},
+ { "sentry.release", new ValueTypePair("1.0.0", "string")},
+ { "sentry.trace.parent_span_id", new ValueTypePair("b0e6f15b45c36b12", "string")},
};
+
+ writer.WriteStartObject();
+ writer.WriteStartArray("items");
+ writer.WriteStartObject();
+
+ writer.WriteNumber("timestamp", Timestamp.ToUnixTimeSeconds());
+ writer.WriteString("trace_id", TraceId);
+ writer.WriteString("level", Level.ToLogString());
+ writer.WriteString("body", Message);
+
+ writer.WritePropertyName("attributes");
+ writer.WriteStartObject();
+
if (Template is not null)
{
- Attributes["sentry.message.template"] = new ValueTypePair("User %s has logged in!", "string");
+ writer.WriteSerializable("sentry.message.template", new ValueTypePair(Template, "string"), null);
}
if (Parameters is not null)
{
for (var index = 0; index < Parameters.Length; index++)
{
- Attributes[$"sentry.message.parameters.{index}"] = new ValueTypePair(Parameters[index], "string");
+ var type = "string";
+ writer.WriteSerializable($"sentry.message.parameters.{index}", new ValueTypePair(Parameters[index], type), null);
}
}
- writer.WriteStartObject();
-
- writer.WriteStartArray("items");
-
- writer.WriteStartObject();
+ if (_attributes is not null)
+ {
+ foreach (var attribute in _attributes)
+ {
+ writer.WriteSerializable(attribute.Key, attribute.Value, null);
+ }
+ }
- writer.WriteNumber("timestamp", Timestamp.ToUnixTimeSeconds());
- writer.WriteString("trace_id", TraceId);
- writer.WriteString("level", Level.ToLogString());
- writer.WriteString("body", Message);
- writer.WriteDictionaryIfNotEmpty("attributes", Attributes, logger);
+ writer.WriteEndObject();
if (SeverityNumber != -1)
{
@@ -72,13 +126,12 @@ public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
}
writer.WriteEndObject();
-
writer.WriteEndArray();
-
writer.WriteEndObject();
}
}
+//TODO: remove? perhaps a simple System.ValueTuple`2 suffices
internal readonly struct ValueTypePair : ISentryJsonSerializable
{
public ValueTypePair(object value, string type)
diff --git a/src/Sentry/Experimental/SentrySeverity.cs b/src/Sentry/Experimental/SentrySeverity.cs
index 92bcdd5c46..189d8eac6c 100644
--- a/src/Sentry/Experimental/SentrySeverity.cs
+++ b/src/Sentry/Experimental/SentrySeverity.cs
@@ -1,8 +1,11 @@
using Sentry.Infrastructure;
+#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
+
namespace Sentry.Experimental;
-#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
+//TODO: QUESTION: not sure about the name
+// this is a bit different to Sentry.SentryLevel and Sentry.BreadcrumbLevel
[Experimental(DiagnosticId.ExperimentalSentryLogs)]
public enum SentrySeverity : short
{
@@ -13,12 +16,11 @@ public enum SentrySeverity : short
Error,
Fatal,
}
-#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
[Experimental(DiagnosticId.ExperimentalSentryLogs)]
internal static class SentrySeverityExtensions
{
- public static string ToLogString(this SentrySeverity severity)
+ internal static string ToLogString(this SentrySeverity severity)
{
return severity switch
{
@@ -31,4 +33,42 @@ public static string ToLogString(this SentrySeverity severity)
_ => throw new ArgumentOutOfRangeException(nameof(severity), severity, null),
};
}
+
+ internal static SentrySeverity FromSeverityNumber(int severityNumber)
+ {
+ ThrowIfOutOfRange(severityNumber);
+
+ return severityNumber switch
+ {
+ >= 1 and <= 4 => SentrySeverity.Trace,
+ >= 5 and <= 8 => SentrySeverity.Debug,
+ >= 9 and <= 12 => SentrySeverity.Info,
+ >= 13 and <= 16 => SentrySeverity.Warn,
+ >= 17 and <= 20 => SentrySeverity.Error,
+ >= 21 and <= 24 => SentrySeverity.Fatal,
+ _ => throw new UnreachableException(),
+ };
+ }
+
+ internal static int ToSeverityNumber(SentrySeverity severity)
+ {
+ return severity switch
+ {
+ SentrySeverity.Trace => 1,
+ SentrySeverity.Debug => 5,
+ SentrySeverity.Info => 9,
+ SentrySeverity.Warn => 13,
+ SentrySeverity.Error => 17,
+ SentrySeverity.Fatal => 21,
+ _ => throw new ArgumentOutOfRangeException(nameof(severity), severity, null)
+ };
+ }
+
+ internal static void ThrowIfOutOfRange(int severityNumber)
+ {
+ if (severityNumber is < 1 or > 24)
+ {
+ throw new ArgumentOutOfRangeException(nameof(severityNumber), severityNumber, "SeverityNumber must be between 1 (inclusive) and 24 (inclusive).");
+ }
+ }
}
diff --git a/src/Sentry/Experimental/System.Diagnostics.UnreachableException.cs b/src/Sentry/Experimental/System.Diagnostics.UnreachableException.cs
new file mode 100644
index 0000000000..48b51df92e
--- /dev/null
+++ b/src/Sentry/Experimental/System.Diagnostics.UnreachableException.cs
@@ -0,0 +1,22 @@
+#if !NET7_0_OR_GREATER
+// ReSharper disable CheckNamespace
+namespace System.Diagnostics;
+
+internal sealed class UnreachableException : Exception
+{
+ public UnreachableException()
+ : base("The program executed an instruction that was thought to be unreachable.")
+ {
+ }
+
+ public UnreachableException(string? message)
+ : base(message ?? "The program executed an instruction that was thought to be unreachable.")
+ {
+ }
+
+ public UnreachableException(string? message, Exception? innerException)
+ : base(message ?? "The program executed an instruction that was thought to be unreachable.", innerException)
+ {
+ }
+}
+#endif
diff --git a/src/Sentry/Infrastructure/DiagnosticId.cs b/src/Sentry/Infrastructure/DiagnosticId.cs
index ebd17b51d2..b85f489414 100644
--- a/src/Sentry/Infrastructure/DiagnosticId.cs
+++ b/src/Sentry/Infrastructure/DiagnosticId.cs
@@ -9,5 +9,12 @@ internal static class DiagnosticId
internal const string ExperimentalFeature = "SENTRY0001";
#endif
+ //TODO: QUESTION: Should we re-use the above for all [Experimental] features or have one ID per experimental feature?
internal const string ExperimentalSentryLogs = "SENTRY0002";
}
+
+//TODO: not sure about this type name
+internal static class UrlFormats
+{
+ internal const string ExperimentalSentryLogs = "https://github.com/getsentry/sentry-dotnet/issues/4132";
+}
diff --git a/src/Sentry/Internal/Extensions/JsonExtensions.cs b/src/Sentry/Internal/Extensions/JsonExtensions.cs
index 96b28bf81b..435e9e441f 100644
--- a/src/Sentry/Internal/Extensions/JsonExtensions.cs
+++ b/src/Sentry/Internal/Extensions/JsonExtensions.cs
@@ -472,6 +472,17 @@ public static void WriteSerializable(
writer.WriteSerializableValue(value, logger);
}
+ public static void WriteSerializable(
+ this Utf8JsonWriter writer,
+ string propertyName,
+ TValue value,
+ IDiagnosticLogger? logger)
+ where TValue : struct, ISentryJsonSerializable
+ {
+ writer.WritePropertyName(propertyName);
+ value.WriteTo(writer, logger);
+ }
+
public static void WriteDynamicValue(
this Utf8JsonWriter writer,
object? value,
diff --git a/src/Sentry/SentryLogger.cs b/src/Sentry/SentryLogger.cs
new file mode 100644
index 0000000000..af931f9761
--- /dev/null
+++ b/src/Sentry/SentryLogger.cs
@@ -0,0 +1,68 @@
+using Sentry.Experimental;
+using Sentry.Infrastructure;
+using Sentry.Protocol.Envelopes;
+
+//TODO: add XML docs
+#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
+
+namespace Sentry;
+
+///
+/// Creates and sends logs to Sentry.
+///
+[Experimental(DiagnosticId.ExperimentalSentryLogs, UrlFormat = UrlFormats.ExperimentalSentryLogs)]
+public sealed class SentryLogger
+{
+ //TODO: QUESTION: Trace vs LogTrace
+ // Trace() is from the Sentry Logs feature specs. LogTrace() would be more .NET idiomatic
+ public void Trace(string template, object[]? parameters = null, Action? configureLog = null)
+ {
+ CaptureLog(SentrySeverity.Trace, template, parameters, configureLog);
+ }
+
+ //TODO: QUESTION: parameter name "template" vs "format"
+ // "template" from the "sentry.message.template" attributes of the envelope
+ // "format" as in System.String.Format to be more idiomatic
+ public void Debug(string template, object[]? parameters = null, Action? configureLog = null)
+ {
+ CaptureLog(SentrySeverity.Debug, template, parameters, configureLog);
+ }
+
+ public void Info(string template, object[]? parameters = null, Action? configureLog = null)
+ {
+ CaptureLog(SentrySeverity.Info, template, parameters, configureLog);
+ }
+
+ //TODO: QUESTION: Warn vs Warning
+ // Warn is from the Sentry Logs feature specs. Warning would be more .NET idiomatic
+ public void Warn(string template, object[]? parameters = null, Action? configureLog = null)
+ {
+ CaptureLog(SentrySeverity.Warn, template, parameters, configureLog);
+ }
+
+ public void Error(string template, object[]? parameters = null, Action? configureLog = null)
+ {
+ CaptureLog(SentrySeverity.Error, template, parameters, configureLog);
+ }
+
+ public void Fatal(string template, object[]? parameters = null, Action? configureLog = null)
+ {
+ CaptureLog(SentrySeverity.Fatal, template, parameters, configureLog);
+ }
+
+ //TODO: consider ReadOnlySpan for TFMs where Span is available
+ // or: utilize a custom [InterpolatedStringHandler] for modern TFMs
+ // with which we may not be able to enforce on compile-time to only support string, boolean, integer, double
+ // but we could have an Analyzer for that, indicating that Sentry does not support other types if used in the interpolated string
+ // or: utilize a SourceGen, similar to the Microsoft.Extensions.Logging [LoggerMessage]
+ // with which we could enforce on compile-time to only support string, boolean, integer, double
+ private static void CaptureLog(SentrySeverity level, string template, object[]? parameters, Action? configureLog)
+ {
+ string message = String.Format(template, parameters ?? []);
+ SentryLog log = new(level, message);
+ configureLog?.Invoke(log);
+
+ IHub hub = SentrySdk.CurrentHub;
+ _ = hub.CaptureEnvelope(Envelope.FromLog(log));
+ }
+}
diff --git a/src/Sentry/SentrySdk.cs b/src/Sentry/SentrySdk.cs
index 401a0fa6f0..cabb8334a0 100644
--- a/src/Sentry/SentrySdk.cs
+++ b/src/Sentry/SentrySdk.cs
@@ -279,6 +279,15 @@ public void Dispose()
///
public static bool IsEnabled { [DebuggerStepThrough] get => CurrentHub.IsEnabled; }
+ ///
+ /// Creates and sends logs to Sentry.
+ ///
+ //TODO: add to IHub or ISentryClient
+ // adding to interfaces is breaking, perhaps via a DIM but what about netstandard2.0 runtimes
+ // or are these interfaces intended to be extended as user code is not meant to implement them
+ [Experimental(DiagnosticId.ExperimentalSentryLogs, UrlFormat = UrlFormats.ExperimentalSentryLogs)]
+ public static SentryLogger Logger { get; } = new SentryLogger();
+
///
/// Creates a new scope that will terminate when disposed.
///
diff --git a/test/Sentry.DiagnosticSource.IntegrationTests/SqlListenerTests.verify.cs b/test/Sentry.DiagnosticSource.IntegrationTests/SqlListenerTests.verify.cs
index b9f4250999..afc7ed5a5d 100644
--- a/test/Sentry.DiagnosticSource.IntegrationTests/SqlListenerTests.verify.cs
+++ b/test/Sentry.DiagnosticSource.IntegrationTests/SqlListenerTests.verify.cs
@@ -155,7 +155,7 @@ public void ShouldIgnoreAllErrorAndExceptionIds()
foreach (var field in eventIds)
{
var eventId = (EventId)field.GetValue(null)!;
- var isEfExceptionMessage = SentryLogger.IsEfExceptionMessage(eventId);
+ var isEfExceptionMessage = Sentry.Extensions.Logging.SentryLogger.IsEfExceptionMessage(eventId);
var name = field.Name;
if (name.EndsWith("Exception") ||
name.EndsWith("Error") ||
From d96b092acede1f00892c6ca4d3e599d4ddcdf384 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20P=C3=B6lz?=
<38893694+Flash0ver@users.noreply.github.com>
Date: Mon, 5 May 2025 14:08:43 +0200
Subject: [PATCH 003/101] style(logs): consolidate
---
src/Sentry/Experimental/SentryLog.cs | 24 ----------------------
src/Sentry/Experimental/ValueTypePair.cs | 26 ++++++++++++++++++++++++
src/Sentry/SentryLogger.cs | 4 ++--
3 files changed, 28 insertions(+), 26 deletions(-)
create mode 100644 src/Sentry/Experimental/ValueTypePair.cs
diff --git a/src/Sentry/Experimental/SentryLog.cs b/src/Sentry/Experimental/SentryLog.cs
index fde5a3106f..f7bdf687ed 100644
--- a/src/Sentry/Experimental/SentryLog.cs
+++ b/src/Sentry/Experimental/SentryLog.cs
@@ -45,7 +45,6 @@ public required int SeverityNumber
get => _severityNumber;
set
{
- //
SentrySeverityExtensions.ThrowIfOutOfRange(value);
_severityNumber = value;
}
@@ -130,26 +129,3 @@ public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
writer.WriteEndObject();
}
}
-
-//TODO: remove? perhaps a simple System.ValueTuple`2 suffices
-internal readonly struct ValueTypePair : ISentryJsonSerializable
-{
- public ValueTypePair(object value, string type)
- {
- Value = value.ToString()!;
- Type = type;
- }
-
- public string Value { get; }
- public string Type { get; }
-
- public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
- {
- writer.WriteStartObject();
-
- writer.WriteString("value", Value);
- writer.WriteString("type", Type);
-
- writer.WriteEndObject();
- }
-}
diff --git a/src/Sentry/Experimental/ValueTypePair.cs b/src/Sentry/Experimental/ValueTypePair.cs
new file mode 100644
index 0000000000..f1d41a9a15
--- /dev/null
+++ b/src/Sentry/Experimental/ValueTypePair.cs
@@ -0,0 +1,26 @@
+using Sentry.Extensibility;
+
+namespace Sentry.Experimental;
+
+//TODO: remove? perhaps a simple System.ValueTuple`2 suffices
+internal readonly struct ValueTypePair : ISentryJsonSerializable
+{
+ public ValueTypePair(object value, string type)
+ {
+ Value = value.ToString()!;
+ Type = type;
+ }
+
+ public string Value { get; }
+ public string Type { get; }
+
+ public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
+ {
+ writer.WriteStartObject();
+
+ writer.WriteString("value", Value);
+ writer.WriteString("type", Type);
+
+ writer.WriteEndObject();
+ }
+}
diff --git a/src/Sentry/SentryLogger.cs b/src/Sentry/SentryLogger.cs
index af931f9761..9595f06733 100644
--- a/src/Sentry/SentryLogger.cs
+++ b/src/Sentry/SentryLogger.cs
@@ -58,11 +58,11 @@ public void Fatal(string template, object[]? parameters = null, Action? configureLog)
{
- string message = String.Format(template, parameters ?? []);
+ var message = string.Format(template, parameters ?? []);
SentryLog log = new(level, message);
configureLog?.Invoke(log);
- IHub hub = SentrySdk.CurrentHub;
+ var hub = SentrySdk.CurrentHub;
_ = hub.CaptureEnvelope(Envelope.FromLog(log));
}
}
From 2958a47de2606be8cc11fd8c63e6df559d3ea1b2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20P=C3=B6lz?=
<38893694+Flash0ver@users.noreply.github.com>
Date: Mon, 5 May 2025 15:33:54 +0200
Subject: [PATCH 004/101] ref(logs): remove generic WriteSerializable overload
---
src/Sentry/Internal/Extensions/JsonExtensions.cs | 11 -----------
1 file changed, 11 deletions(-)
diff --git a/src/Sentry/Internal/Extensions/JsonExtensions.cs b/src/Sentry/Internal/Extensions/JsonExtensions.cs
index 435e9e441f..96b28bf81b 100644
--- a/src/Sentry/Internal/Extensions/JsonExtensions.cs
+++ b/src/Sentry/Internal/Extensions/JsonExtensions.cs
@@ -472,17 +472,6 @@ public static void WriteSerializable(
writer.WriteSerializableValue(value, logger);
}
- public static void WriteSerializable(
- this Utf8JsonWriter writer,
- string propertyName,
- TValue value,
- IDiagnosticLogger? logger)
- where TValue : struct, ISentryJsonSerializable
- {
- writer.WritePropertyName(propertyName);
- value.WriteTo(writer, logger);
- }
-
public static void WriteDynamicValue(
this Utf8JsonWriter writer,
object? value,
From a63371bd30115738dbc9a80977f3952b56a170e0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20P=C3=B6lz?=
<38893694+Flash0ver@users.noreply.github.com>
Date: Mon, 5 May 2025 19:56:28 +0200
Subject: [PATCH 005/101] ref(logs): consolidate experimental Diagnostic-ID
---
samples/Sentry.Samples.Console.Basic/Program.cs | 2 --
src/Sentry/Experimental/SentryLog.cs | 2 +-
src/Sentry/Experimental/SentrySeverity.cs | 4 ++--
src/Sentry/Infrastructure/DiagnosticId.cs | 11 -----------
src/Sentry/Protocol/Envelopes/Envelope.cs | 2 +-
src/Sentry/Protocol/Envelopes/EnvelopeItem.cs | 2 +-
src/Sentry/SentryLogger.cs | 2 +-
src/Sentry/SentrySdk.cs | 2 +-
8 files changed, 7 insertions(+), 20 deletions(-)
diff --git a/samples/Sentry.Samples.Console.Basic/Program.cs b/samples/Sentry.Samples.Console.Basic/Program.cs
index 7fab354823..04e5699dc4 100644
--- a/samples/Sentry.Samples.Console.Basic/Program.cs
+++ b/samples/Sentry.Samples.Console.Basic/Program.cs
@@ -36,14 +36,12 @@
options.TracesSampleRate = 1.0;
});
-#pragma warning disable SENTRY0002
SentrySdk.Logger.Trace("Hello, World!", null, log => log.SetAttribute("trace", "trace"));
SentrySdk.Logger.Debug("Hello, .NET!", null, log => log.SetAttribute("trace", "trace"));
SentrySdk.Logger.Info("Information", null, log => log.SetAttribute("trace", "trace"));
SentrySdk.Logger.Warn("Warning with one {0}", ["parameter"], log => log.SetAttribute("trace", "trace"));
SentrySdk.Logger.Error("Error with {0} {1}", [2, "parameters"], log => log.SetAttribute("trace", "trace"));
SentrySdk.Logger.Fatal("Fatal {0} and {1}", [true, false], log => log.SetAttribute("trace", "trace"));
-#pragma warning restore SENTRY0002
await Task.Delay(5_000);
diff --git a/src/Sentry/Experimental/SentryLog.cs b/src/Sentry/Experimental/SentryLog.cs
index f7bdf687ed..fab804745d 100644
--- a/src/Sentry/Experimental/SentryLog.cs
+++ b/src/Sentry/Experimental/SentryLog.cs
@@ -6,7 +6,7 @@
namespace Sentry.Experimental;
-[Experimental(DiagnosticId.ExperimentalSentryLogs)]
+[Experimental(DiagnosticId.ExperimentalFeature)]
public sealed class SentryLog : ISentryJsonSerializable
{
private Dictionary? _attributes;
diff --git a/src/Sentry/Experimental/SentrySeverity.cs b/src/Sentry/Experimental/SentrySeverity.cs
index 189d8eac6c..ad70d97c65 100644
--- a/src/Sentry/Experimental/SentrySeverity.cs
+++ b/src/Sentry/Experimental/SentrySeverity.cs
@@ -6,7 +6,7 @@ namespace Sentry.Experimental;
//TODO: QUESTION: not sure about the name
// this is a bit different to Sentry.SentryLevel and Sentry.BreadcrumbLevel
-[Experimental(DiagnosticId.ExperimentalSentryLogs)]
+[Experimental(DiagnosticId.ExperimentalFeature)]
public enum SentrySeverity : short
{
Trace,
@@ -17,7 +17,7 @@ public enum SentrySeverity : short
Fatal,
}
-[Experimental(DiagnosticId.ExperimentalSentryLogs)]
+[Experimental(DiagnosticId.ExperimentalFeature)]
internal static class SentrySeverityExtensions
{
internal static string ToLogString(this SentrySeverity severity)
diff --git a/src/Sentry/Infrastructure/DiagnosticId.cs b/src/Sentry/Infrastructure/DiagnosticId.cs
index b85f489414..c5bd026784 100644
--- a/src/Sentry/Infrastructure/DiagnosticId.cs
+++ b/src/Sentry/Infrastructure/DiagnosticId.cs
@@ -2,19 +2,8 @@ namespace Sentry.Infrastructure;
internal static class DiagnosticId
{
-#if NET5_0_OR_GREATER
///
/// Indicates that the feature is experimental and may be subject to change or removal in future versions.
///
internal const string ExperimentalFeature = "SENTRY0001";
-#endif
-
- //TODO: QUESTION: Should we re-use the above for all [Experimental] features or have one ID per experimental feature?
- internal const string ExperimentalSentryLogs = "SENTRY0002";
-}
-
-//TODO: not sure about this type name
-internal static class UrlFormats
-{
- internal const string ExperimentalSentryLogs = "https://github.com/getsentry/sentry-dotnet/issues/4132";
}
diff --git a/src/Sentry/Protocol/Envelopes/Envelope.cs b/src/Sentry/Protocol/Envelopes/Envelope.cs
index 0fb07b47d2..e678aaac80 100644
--- a/src/Sentry/Protocol/Envelopes/Envelope.cs
+++ b/src/Sentry/Protocol/Envelopes/Envelope.cs
@@ -446,7 +446,7 @@ internal static Envelope FromClientReport(ClientReport clientReport)
return new Envelope(header, items);
}
- [Experimental(DiagnosticId.ExperimentalSentryLogs)]
+ [Experimental(DiagnosticId.ExperimentalFeature)]
internal static Envelope FromLog(SentryLog log)
{
var header = DefaultHeader;
diff --git a/src/Sentry/Protocol/Envelopes/EnvelopeItem.cs b/src/Sentry/Protocol/Envelopes/EnvelopeItem.cs
index a73f9e4ab8..0b4c6d6ac4 100644
--- a/src/Sentry/Protocol/Envelopes/EnvelopeItem.cs
+++ b/src/Sentry/Protocol/Envelopes/EnvelopeItem.cs
@@ -373,7 +373,7 @@ internal static EnvelopeItem FromClientReport(ClientReport report)
return new EnvelopeItem(header, new JsonSerializable(report));
}
- [Experimental(DiagnosticId.ExperimentalSentryLogs)]
+ [Experimental(DiagnosticId.ExperimentalFeature)]
internal static EnvelopeItem FromLog(SentryLog log)
{
var header = new Dictionary(3, StringComparer.Ordinal)
diff --git a/src/Sentry/SentryLogger.cs b/src/Sentry/SentryLogger.cs
index 9595f06733..41b92cf9b0 100644
--- a/src/Sentry/SentryLogger.cs
+++ b/src/Sentry/SentryLogger.cs
@@ -10,7 +10,7 @@ namespace Sentry;
///
/// Creates and sends logs to Sentry.
///
-[Experimental(DiagnosticId.ExperimentalSentryLogs, UrlFormat = UrlFormats.ExperimentalSentryLogs)]
+[Experimental(DiagnosticId.ExperimentalFeature)]
public sealed class SentryLogger
{
//TODO: QUESTION: Trace vs LogTrace
diff --git a/src/Sentry/SentrySdk.cs b/src/Sentry/SentrySdk.cs
index cabb8334a0..670d22d052 100644
--- a/src/Sentry/SentrySdk.cs
+++ b/src/Sentry/SentrySdk.cs
@@ -285,7 +285,7 @@ public void Dispose()
//TODO: add to IHub or ISentryClient
// adding to interfaces is breaking, perhaps via a DIM but what about netstandard2.0 runtimes
// or are these interfaces intended to be extended as user code is not meant to implement them
- [Experimental(DiagnosticId.ExperimentalSentryLogs, UrlFormat = UrlFormats.ExperimentalSentryLogs)]
+ [Experimental(DiagnosticId.ExperimentalFeature)]
public static SentryLogger Logger { get; } = new SentryLogger();
///
From d8d2567233cc63b793b799b0a3d72fc61b1ea6f9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20P=C3=B6lz?=
<38893694+Flash0ver@users.noreply.github.com>
Date: Wed, 7 May 2025 11:42:30 +0200
Subject: [PATCH 006/101] feat(logs): add experimental options
---
.../Sentry.Samples.Console.Basic/Program.cs | 27 ++-
src/Sentry/Experimental/SentryLog.cs | 157 ++++++++++++++++--
src/Sentry/Experimental/ValueTypePair.cs | 26 ---
src/Sentry/SentryLogger.cs | 114 +++++++++++--
src/Sentry/SentryOptions.cs | 50 ++++++
5 files changed, 314 insertions(+), 60 deletions(-)
delete mode 100644 src/Sentry/Experimental/ValueTypePair.cs
diff --git a/samples/Sentry.Samples.Console.Basic/Program.cs b/samples/Sentry.Samples.Console.Basic/Program.cs
index 04e5699dc4..6c6f89f360 100644
--- a/samples/Sentry.Samples.Console.Basic/Program.cs
+++ b/samples/Sentry.Samples.Console.Basic/Program.cs
@@ -34,16 +34,29 @@
// This option tells Sentry to capture 100% of traces. You still need to start transactions and spans.
options.TracesSampleRate = 1.0;
+
+ options.EnableLogs = true;
+ options.SetBeforeSendLog(static (SentryLog log) =>
+ {
+ //TODO: this feels a bit off ... perhaps a "TryGet{Type}Attribute" method group could help here instead of exposing the boxing object-TValue-based Dictionary`2
+ if (log.Attributes.TryGetValue("plan.type", out var attribute) && attribute is "enterprise")
+ {
+ return null;
+ }
+
+ return log.SeverityNumber is >= 17 and <= 20 ? log : null;
+ });
+ options.LogsSampleRate = 1.0f;
});
-SentrySdk.Logger.Trace("Hello, World!", null, log => log.SetAttribute("trace", "trace"));
-SentrySdk.Logger.Debug("Hello, .NET!", null, log => log.SetAttribute("trace", "trace"));
-SentrySdk.Logger.Info("Information", null, log => log.SetAttribute("trace", "trace"));
-SentrySdk.Logger.Warn("Warning with one {0}", ["parameter"], log => log.SetAttribute("trace", "trace"));
-SentrySdk.Logger.Error("Error with {0} {1}", [2, "parameters"], log => log.SetAttribute("trace", "trace"));
-SentrySdk.Logger.Fatal("Fatal {0} and {1}", [true, false], log => log.SetAttribute("trace", "trace"));
+SentrySdk.Logger.Trace("Hello, World!", null, log => log.SetAttribute("trace-key", "trace-value"));
+SentrySdk.Logger.Debug("Hello, .NET!", null, log => log.SetAttribute("debug-key", "debug-value"));
+SentrySdk.Logger.Info("Information", null, log => log.SetAttribute("info-key", "info-value"));
+SentrySdk.Logger.Warn("Warning with one {0}", ["parameter"], log => log.SetAttribute("warn-key", "warn-value"));
+SentrySdk.Logger.Error("Error with {0} {1}", [2, "parameters"], log => log.SetAttribute("error-key", "error-value"));
+SentrySdk.Logger.Fatal("Fatal {0} and {1}", [true, false], log => log.SetAttribute("fatal-key", "fatal-value"));
-await Task.Delay(5_000);
+await Task.Delay(TimeSpan.FromSeconds(5));
/*
// This starts a new transaction and attaches it to the scope.
diff --git a/src/Sentry/Experimental/SentryLog.cs b/src/Sentry/Experimental/SentryLog.cs
index fab804745d..5c3e0e1785 100644
--- a/src/Sentry/Experimental/SentryLog.cs
+++ b/src/Sentry/Experimental/SentryLog.cs
@@ -1,6 +1,6 @@
using Sentry.Extensibility;
using Sentry.Infrastructure;
-using Sentry.Internal.Extensions;
+using Sentry.Internal;
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
@@ -13,13 +13,12 @@ public sealed class SentryLog : ISentryJsonSerializable
private int _severityNumber = -1;
[SetsRequiredMembers]
- internal SentryLog(SentrySeverity level, string message, object[]? parameters = null)
+ internal SentryLog(DateTimeOffset timestamp, SentryId traceId, SentrySeverity level, string message)
{
- Timestamp = DateTimeOffset.UtcNow;
- TraceId = SentryId.Empty;
+ Timestamp = timestamp;
+ TraceId = traceId;
Level = level;
Message = message;
- Parameters = parameters;
}
public required DateTimeOffset Timestamp { get; init; }
@@ -34,7 +33,15 @@ public SentrySeverity Level
public required string Message { get; init; }
- //public Dictionary? Attributes { get { return _attributes; } }
+ public IReadOnlyDictionary Attributes
+ {
+ get
+ {
+ return _attributes is null
+ ? []
+ : _attributes.ToDictionary(static item => item.Key, item => item.Value.Value);
+ }
+ }
public string? Template { get; init; }
@@ -74,15 +81,40 @@ public void SetAttribute(string key, double value)
_attributes[key] = new ValueTypePair(value, "double");
}
- public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
+ internal void SetAttributes(IHub hub, IInternalScopeManager? scopeManager, SentryOptions options)
{
- _attributes = new Dictionary
+ var environment = options.SettingLocator.GetEnvironment();
+ SetAttribute("sentry.environment", environment);
+
+ var release = options.SettingLocator.GetRelease();
+ if (release is not null)
+ {
+ SetAttribute("sentry.release", release);
+ }
+
+ if (hub.GetSpan() is {} span && span.ParentSpanId.HasValue)
{
- { "sentry.environment", new ValueTypePair("production", "string")},
- { "sentry.release", new ValueTypePair("1.0.0", "string")},
- { "sentry.trace.parent_span_id", new ValueTypePair("b0e6f15b45c36b12", "string")},
- };
+ SetAttribute("sentry.trace.parent_span_id", span.ParentSpanId.Value.ToString());
+ }
+ else if (scopeManager is not null)
+ {
+ var currentScope = scopeManager.GetCurrent().Key;
+ var parentSpanId = currentScope.PropagationContext.ParentSpanId;
+ if (parentSpanId.HasValue)
+ {
+ SetAttribute("sentry.trace.parent_span_id", parentSpanId.Value.ToString());
+ }
+ }
+
+ SetAttribute("sentry.sdk.name", Constants.SdkName);
+ if (SdkVersion.Instance.Version is {} version)
+ {
+ SetAttribute("sentry.sdk.version", version);
+ }
+ }
+ public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
+ {
writer.WriteStartObject();
writer.WriteStartArray("items");
writer.WriteStartObject();
@@ -97,15 +129,14 @@ public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
if (Template is not null)
{
- writer.WriteSerializable("sentry.message.template", new ValueTypePair(Template, "string"), null);
+ WriteAttribute(writer, "sentry.message.template", Template, "string");
}
if (Parameters is not null)
{
for (var index = 0; index < Parameters.Length; index++)
{
- var type = "string";
- writer.WriteSerializable($"sentry.message.parameters.{index}", new ValueTypePair(Parameters[index], type), null);
+ WriteAttribute(writer, $"sentry.message.parameters.{index}", Parameters[index], null);
}
}
@@ -113,7 +144,7 @@ public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
{
foreach (var attribute in _attributes)
{
- writer.WriteSerializable(attribute.Key, attribute.Value, null);
+ WriteAttribute(writer, attribute.Key, attribute.Value);
}
}
@@ -128,4 +159,98 @@ public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
writer.WriteEndArray();
writer.WriteEndObject();
}
+
+ private static void WriteAttribute(Utf8JsonWriter writer, string propertyName, ValueTypePair attribute)
+ {
+ writer.WritePropertyName(propertyName);
+ if (attribute.Type is not null)
+ {
+ WriteAttributeValue(writer, attribute.Value, attribute.Type);
+ }
+ else
+ {
+ WriteAttributeValue(writer, attribute.Value);
+ }
+ }
+
+ private static void WriteAttribute(Utf8JsonWriter writer, string propertyName, object value, string? type)
+ {
+ writer.WritePropertyName(propertyName);
+ if (type is not null)
+ {
+ WriteAttributeValue(writer, value, type);
+ }
+ else
+ {
+ WriteAttributeValue(writer, value);
+ }
+ }
+
+ private static void WriteAttributeValue(Utf8JsonWriter writer, object value, string type)
+ {
+ writer.WriteStartObject();
+
+ if (type == "string")
+ {
+ writer.WriteString("value", (string)value);
+ writer.WriteString("type", type);
+ }
+ else if (type == "boolean")
+ {
+ writer.WriteBoolean("value", (bool)value);
+ writer.WriteString("type", type);
+ }
+ else if (type == "integer")
+ {
+ writer.WriteNumber("value", (int)value);
+ writer.WriteString("type", type);
+ }
+ else if (type == "double")
+ {
+ writer.WriteNumber("value", (double)value);
+ writer.WriteString("type", type);
+ }
+ else
+ {
+ writer.WriteString("value", value.ToString());
+ writer.WriteString("type", "string");
+ }
+
+ writer.WriteEndObject();
+ }
+
+ private static void WriteAttributeValue(Utf8JsonWriter writer, object value)
+ {
+ writer.WriteStartObject();
+
+ if (value is string str)
+ {
+ writer.WriteString("value", str);
+ writer.WriteString("type", "string");
+ }
+ else if (value is bool boolean)
+ {
+ writer.WriteBoolean("value", boolean);
+ writer.WriteString("type", "boolean");
+ }
+ else if (value is int int32)
+ {
+ writer.WriteNumber("value", int32);
+ writer.WriteString("type", "integer");
+ }
+ else if (value is double float64)
+ {
+ writer.WriteNumber("value", float64);
+ writer.WriteString("type", "double");
+ }
+ else
+ {
+ writer.WriteString("value", value.ToString());
+ writer.WriteString("type", "string");
+ }
+
+ writer.WriteEndObject();
+ }
+
+ private record struct ValueTypePair(object Value, string? Type);
}
diff --git a/src/Sentry/Experimental/ValueTypePair.cs b/src/Sentry/Experimental/ValueTypePair.cs
deleted file mode 100644
index f1d41a9a15..0000000000
--- a/src/Sentry/Experimental/ValueTypePair.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using Sentry.Extensibility;
-
-namespace Sentry.Experimental;
-
-//TODO: remove? perhaps a simple System.ValueTuple`2 suffices
-internal readonly struct ValueTypePair : ISentryJsonSerializable
-{
- public ValueTypePair(object value, string type)
- {
- Value = value.ToString()!;
- Type = type;
- }
-
- public string Value { get; }
- public string Type { get; }
-
- public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
- {
- writer.WriteStartObject();
-
- writer.WriteString("value", Value);
- writer.WriteString("type", Type);
-
- writer.WriteEndObject();
- }
-}
diff --git a/src/Sentry/SentryLogger.cs b/src/Sentry/SentryLogger.cs
index 41b92cf9b0..bdc09bd2d3 100644
--- a/src/Sentry/SentryLogger.cs
+++ b/src/Sentry/SentryLogger.cs
@@ -1,5 +1,6 @@
using Sentry.Experimental;
using Sentry.Infrastructure;
+using Sentry.Internal;
using Sentry.Protocol.Envelopes;
//TODO: add XML docs
@@ -13,11 +14,21 @@ namespace Sentry;
[Experimental(DiagnosticId.ExperimentalFeature)]
public sealed class SentryLogger
{
+ private readonly RandomValuesFactory _randomValuesFactory;
+
+ internal SentryLogger()
+ {
+ _randomValuesFactory = new SynchronizedRandomValuesFactory();
+ }
+
//TODO: QUESTION: Trace vs LogTrace
// Trace() is from the Sentry Logs feature specs. LogTrace() would be more .NET idiomatic
public void Trace(string template, object[]? parameters = null, Action? configureLog = null)
{
- CaptureLog(SentrySeverity.Trace, template, parameters, configureLog);
+ if (IsEnabled())
+ {
+ CaptureLog(SentrySeverity.Trace, template, parameters, configureLog);
+ }
}
//TODO: QUESTION: parameter name "template" vs "format"
@@ -25,29 +36,56 @@ public void Trace(string template, object[]? parameters = null, Action? configureLog = null)
{
- CaptureLog(SentrySeverity.Debug, template, parameters, configureLog);
+ if (IsEnabled())
+ {
+ CaptureLog(SentrySeverity.Debug, template, parameters, configureLog);
+ }
}
public void Info(string template, object[]? parameters = null, Action? configureLog = null)
{
- CaptureLog(SentrySeverity.Info, template, parameters, configureLog);
+ if (IsEnabled())
+ {
+ CaptureLog(SentrySeverity.Info, template, parameters, configureLog);
+ }
}
//TODO: QUESTION: Warn vs Warning
// Warn is from the Sentry Logs feature specs. Warning would be more .NET idiomatic
public void Warn(string template, object[]? parameters = null, Action? configureLog = null)
{
- CaptureLog(SentrySeverity.Warn, template, parameters, configureLog);
+ if (IsEnabled())
+ {
+ CaptureLog(SentrySeverity.Warn, template, parameters, configureLog);
+ }
}
public void Error(string template, object[]? parameters = null, Action? configureLog = null)
{
- CaptureLog(SentrySeverity.Error, template, parameters, configureLog);
+ if (IsEnabled())
+ {
+ CaptureLog(SentrySeverity.Error, template, parameters, configureLog);
+ }
}
public void Fatal(string template, object[]? parameters = null, Action? configureLog = null)
{
- CaptureLog(SentrySeverity.Fatal, template, parameters, configureLog);
+ if (IsEnabled())
+ {
+ CaptureLog(SentrySeverity.Fatal, template, parameters, configureLog);
+ }
+ }
+
+ private bool IsEnabled()
+ {
+ var hub = SentrySdk.CurrentHub;
+
+ if (hub.GetSentryOptions() is {} options)
+ {
+ return options.EnableLogs;
+ }
+
+ return false;
}
//TODO: consider ReadOnlySpan for TFMs where Span is available
@@ -56,13 +94,67 @@ public void Fatal(string template, object[]? parameters = null, Action? configureLog)
+ private void CaptureLog(SentrySeverity level, string template, object[]? parameters, Action? configureLog)
{
- var message = string.Format(template, parameters ?? []);
- SentryLog log = new(level, message);
- configureLog?.Invoke(log);
+ var timestamp = DateTimeOffset.UtcNow;
var hub = SentrySdk.CurrentHub;
- _ = hub.CaptureEnvelope(Envelope.FromLog(log));
+
+ if (hub.GetSentryOptions() is not { EnableLogs: true } options)
+ {
+ //Logs disabled
+ return;
+ }
+
+ if (!_randomValuesFactory.NextBool(options.LogsSampleRate))
+ {
+ //Log sampled
+ return;
+ }
+
+ //process log (attach attributes)
+
+ var scopeManager = (hub as Hub)?.ScopeManager;
+ SentryId traceId;
+ if (hub.GetSpan() is {} span)
+ {
+ traceId = span.TraceId;
+ }
+ else if (scopeManager is not null)
+ {
+ var currentScope = scopeManager.GetCurrent().Key;
+ traceId = currentScope.PropagationContext.TraceId;
+ }
+ else
+ {
+ traceId = SentryId.Empty;
+ }
+
+ var message = string.Format(template, parameters ?? []);
+ SentryLog log = new(timestamp, traceId, level, message)
+ {
+ Template = template,
+ Parameters = parameters,
+ };
+ log.SetAttributes(hub, scopeManager, options);
+
+ SentryLog? configuredLog;
+ try
+ {
+ configureLog?.Invoke(log);
+ configuredLog = options.BeforeSendLogInternal?.Invoke(log);
+ }
+ catch (Exception e)
+ {
+ //TODO: diagnostic log
+ Console.WriteLine(e);
+ return;
+ }
+
+ if (configuredLog is not null)
+ {
+ //TODO: enqueue in Batch-Processor / Background-Worker
+ _ = hub.CaptureEnvelope(Envelope.FromLog(configuredLog));
+ }
}
}
diff --git a/src/Sentry/SentryOptions.cs b/src/Sentry/SentryOptions.cs
index a3bba2f9c9..cc071ab387 100644
--- a/src/Sentry/SentryOptions.cs
+++ b/src/Sentry/SentryOptions.cs
@@ -1,3 +1,4 @@
+using Sentry.Experimental;
using Sentry.Extensibility;
using Sentry.Http;
using Sentry.Infrastructure;
@@ -518,6 +519,55 @@ public void SetBeforeBreadcrumb(Func beforeBreadcrumb)
_beforeBreadcrumb = (breadcrumb, _) => beforeBreadcrumb(breadcrumb);
}
+ ///
+ /// When set to , logs are sent to Sentry.
+ /// Defaults to .
+ ///
+ [Experimental(DiagnosticId.ExperimentalFeature)]
+ public bool EnableLogs { get; set; } = false;
+
+ [Experimental(DiagnosticId.ExperimentalFeature)]
+ private Func? _beforeSendLog;
+
+ [Experimental(DiagnosticId.ExperimentalFeature)]
+ internal Func? BeforeSendLogInternal => _beforeSendLog;
+
+ ///
+ /// Sets a callback function to be invoked before sending the log to Sentry.
+ ///
+ ///
+ /// It can be used to modify the log object before being sent to Sentry.
+ /// To prevent the log from being sent to Sentry, return .
+ ///
+ [Experimental(DiagnosticId.ExperimentalFeature)]
+ public void SetBeforeSendLog(Func beforeSendLog)
+ {
+ _beforeSendLog = beforeSendLog;
+ }
+
+ [Experimental(DiagnosticId.ExperimentalFeature)]
+ private float _logsSampleRate = 1.0f;
+
+ ///
+ /// A between 0.0f and 1.0f that represents the probability that a log will be sent to Sentry.
+ /// Defaults to 1.0.
+ ///
+ [Experimental(DiagnosticId.ExperimentalFeature)]
+ public float LogsSampleRate
+ {
+ get => _logsSampleRate;
+ set
+ {
+ if (value is < 0.0f or > 1.0f)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), value,
+ "The logs sample rate must be between 0.0 and 1.0, inclusive.");
+ }
+
+ _logsSampleRate = value;
+ }
+ }
+
private int _maxQueueItems = 30;
///
From 165996a7a8f654609d064d0b0507279ce91856e2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20P=C3=B6lz?=
<38893694+Flash0ver@users.noreply.github.com>
Date: Wed, 7 May 2025 12:02:40 +0200
Subject: [PATCH 007/101] ref(logs): remove custom polyfills now provided
through Polyfill
---
...tics.CodeAnalysis.ExperimentalAttribute.cs | 29 -------------------
...System.Diagnostics.UnreachableException.cs | 22 --------------
2 files changed, 51 deletions(-)
delete mode 100644 src/Sentry/Experimental/System.Diagnostics.CodeAnalysis.ExperimentalAttribute.cs
delete mode 100644 src/Sentry/Experimental/System.Diagnostics.UnreachableException.cs
diff --git a/src/Sentry/Experimental/System.Diagnostics.CodeAnalysis.ExperimentalAttribute.cs b/src/Sentry/Experimental/System.Diagnostics.CodeAnalysis.ExperimentalAttribute.cs
deleted file mode 100644
index b173d83323..0000000000
--- a/src/Sentry/Experimental/System.Diagnostics.CodeAnalysis.ExperimentalAttribute.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-#if !NET8_0_OR_GREATER
-// ReSharper disable CheckNamespace
-// ReSharper disable ConvertToPrimaryConstructor
-namespace System.Diagnostics.CodeAnalysis;
-
-[AttributeUsage(AttributeTargets.Assembly |
- AttributeTargets.Module |
- AttributeTargets.Class |
- AttributeTargets.Struct |
- AttributeTargets.Enum |
- AttributeTargets.Constructor |
- AttributeTargets.Method |
- AttributeTargets.Property |
- AttributeTargets.Field |
- AttributeTargets.Event |
- AttributeTargets.Interface |
- AttributeTargets.Delegate, Inherited = false)]
-internal sealed class ExperimentalAttribute : Attribute
-{
- public ExperimentalAttribute(string diagnosticId)
- {
- DiagnosticId = diagnosticId;
- }
-
- public string DiagnosticId { get; }
-
- public string? UrlFormat { get; set; }
-}
-#endif
diff --git a/src/Sentry/Experimental/System.Diagnostics.UnreachableException.cs b/src/Sentry/Experimental/System.Diagnostics.UnreachableException.cs
deleted file mode 100644
index 48b51df92e..0000000000
--- a/src/Sentry/Experimental/System.Diagnostics.UnreachableException.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-#if !NET7_0_OR_GREATER
-// ReSharper disable CheckNamespace
-namespace System.Diagnostics;
-
-internal sealed class UnreachableException : Exception
-{
- public UnreachableException()
- : base("The program executed an instruction that was thought to be unreachable.")
- {
- }
-
- public UnreachableException(string? message)
- : base(message ?? "The program executed an instruction that was thought to be unreachable.")
- {
- }
-
- public UnreachableException(string? message, Exception? innerException)
- : base(message ?? "The program executed an instruction that was thought to be unreachable.", innerException)
- {
- }
-}
-#endif
From 32e7e25bb6264364f3e95849ef1c9cce52ef5f53 Mon Sep 17 00:00:00 2001
From: Sentry Github Bot
Date: Wed, 7 May 2025 10:13:12 +0000
Subject: [PATCH 008/101] Format code
---
src/Sentry/Experimental/SentryLog.cs | 4 ++--
src/Sentry/SentryLogger.cs | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/Sentry/Experimental/SentryLog.cs b/src/Sentry/Experimental/SentryLog.cs
index 5c3e0e1785..232756e0ae 100644
--- a/src/Sentry/Experimental/SentryLog.cs
+++ b/src/Sentry/Experimental/SentryLog.cs
@@ -92,7 +92,7 @@ internal void SetAttributes(IHub hub, IInternalScopeManager? scopeManager, Sentr
SetAttribute("sentry.release", release);
}
- if (hub.GetSpan() is {} span && span.ParentSpanId.HasValue)
+ if (hub.GetSpan() is { } span && span.ParentSpanId.HasValue)
{
SetAttribute("sentry.trace.parent_span_id", span.ParentSpanId.Value.ToString());
}
@@ -107,7 +107,7 @@ internal void SetAttributes(IHub hub, IInternalScopeManager? scopeManager, Sentr
}
SetAttribute("sentry.sdk.name", Constants.SdkName);
- if (SdkVersion.Instance.Version is {} version)
+ if (SdkVersion.Instance.Version is { } version)
{
SetAttribute("sentry.sdk.version", version);
}
diff --git a/src/Sentry/SentryLogger.cs b/src/Sentry/SentryLogger.cs
index bdc09bd2d3..fa4b2b0c0a 100644
--- a/src/Sentry/SentryLogger.cs
+++ b/src/Sentry/SentryLogger.cs
@@ -80,7 +80,7 @@ private bool IsEnabled()
{
var hub = SentrySdk.CurrentHub;
- if (hub.GetSentryOptions() is {} options)
+ if (hub.GetSentryOptions() is { } options)
{
return options.EnableLogs;
}
@@ -116,7 +116,7 @@ private void CaptureLog(SentrySeverity level, string template, object[]? paramet
var scopeManager = (hub as Hub)?.ScopeManager;
SentryId traceId;
- if (hub.GetSpan() is {} span)
+ if (hub.GetSpan() is { } span)
{
traceId = span.TraceId;
}
From 2ba87e4aa85aaca0a56f0a94018a1035d8db7d01 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20P=C3=B6lz?=
<38893694+Flash0ver@users.noreply.github.com>
Date: Wed, 7 May 2025 14:59:51 +0200
Subject: [PATCH 009/101] ref(logs): move types out of Experimental namespace
---
samples/Sentry.Samples.Console.Basic/Program.cs | 2 +-
src/Sentry/Protocol/Envelopes/Envelope.cs | 1 -
src/Sentry/Protocol/Envelopes/EnvelopeItem.cs | 1 -
src/Sentry/{Experimental => Protocol}/SentryLog.cs | 4 ++--
src/Sentry/{Experimental => Protocol}/SentrySeverity.cs | 4 ++--
src/Sentry/SentryLogger.cs | 2 +-
src/Sentry/SentryOptions.cs | 2 +-
7 files changed, 7 insertions(+), 9 deletions(-)
rename src/Sentry/{Experimental => Protocol}/SentryLog.cs (99%)
rename src/Sentry/{Experimental => Protocol}/SentrySeverity.cs (97%)
diff --git a/samples/Sentry.Samples.Console.Basic/Program.cs b/samples/Sentry.Samples.Console.Basic/Program.cs
index 6c6f89f360..dd1972ddeb 100644
--- a/samples/Sentry.Samples.Console.Basic/Program.cs
+++ b/samples/Sentry.Samples.Console.Basic/Program.cs
@@ -9,7 +9,7 @@
*/
using System.Net.Http;
-using Sentry.Experimental;
+using Sentry.Protocol;
using static System.Console;
// Initialize the Sentry SDK. (It is not necessary to dispose it.)
diff --git a/src/Sentry/Protocol/Envelopes/Envelope.cs b/src/Sentry/Protocol/Envelopes/Envelope.cs
index e678aaac80..5f30d3539f 100644
--- a/src/Sentry/Protocol/Envelopes/Envelope.cs
+++ b/src/Sentry/Protocol/Envelopes/Envelope.cs
@@ -1,4 +1,3 @@
-using Sentry.Experimental;
using Sentry.Extensibility;
using Sentry.Infrastructure;
using Sentry.Internal;
diff --git a/src/Sentry/Protocol/Envelopes/EnvelopeItem.cs b/src/Sentry/Protocol/Envelopes/EnvelopeItem.cs
index 0b4c6d6ac4..3e05aa8017 100644
--- a/src/Sentry/Protocol/Envelopes/EnvelopeItem.cs
+++ b/src/Sentry/Protocol/Envelopes/EnvelopeItem.cs
@@ -1,4 +1,3 @@
-using Sentry.Experimental;
using Sentry.Extensibility;
using Sentry.Infrastructure;
using Sentry.Internal;
diff --git a/src/Sentry/Experimental/SentryLog.cs b/src/Sentry/Protocol/SentryLog.cs
similarity index 99%
rename from src/Sentry/Experimental/SentryLog.cs
rename to src/Sentry/Protocol/SentryLog.cs
index 232756e0ae..e6da56e6d6 100644
--- a/src/Sentry/Experimental/SentryLog.cs
+++ b/src/Sentry/Protocol/SentryLog.cs
@@ -4,7 +4,7 @@
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
-namespace Sentry.Experimental;
+namespace Sentry.Protocol;
[Experimental(DiagnosticId.ExperimentalFeature)]
public sealed class SentryLog : ISentryJsonSerializable
@@ -25,7 +25,7 @@ internal SentryLog(DateTimeOffset timestamp, SentryId traceId, SentrySeverity le
public required SentryId TraceId { get; init; }
- public SentrySeverity Level
+ private SentrySeverity Level
{
get => SentrySeverityExtensions.FromSeverityNumber(_severityNumber);
set => _severityNumber = SentrySeverityExtensions.ToSeverityNumber(value);
diff --git a/src/Sentry/Experimental/SentrySeverity.cs b/src/Sentry/Protocol/SentrySeverity.cs
similarity index 97%
rename from src/Sentry/Experimental/SentrySeverity.cs
rename to src/Sentry/Protocol/SentrySeverity.cs
index ad70d97c65..bf3b54daf4 100644
--- a/src/Sentry/Experimental/SentrySeverity.cs
+++ b/src/Sentry/Protocol/SentrySeverity.cs
@@ -2,12 +2,12 @@
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
-namespace Sentry.Experimental;
+namespace Sentry.Protocol;
//TODO: QUESTION: not sure about the name
// this is a bit different to Sentry.SentryLevel and Sentry.BreadcrumbLevel
[Experimental(DiagnosticId.ExperimentalFeature)]
-public enum SentrySeverity : short
+internal enum SentrySeverity : short
{
Trace,
Debug,
diff --git a/src/Sentry/SentryLogger.cs b/src/Sentry/SentryLogger.cs
index fa4b2b0c0a..637a886e52 100644
--- a/src/Sentry/SentryLogger.cs
+++ b/src/Sentry/SentryLogger.cs
@@ -1,6 +1,6 @@
-using Sentry.Experimental;
using Sentry.Infrastructure;
using Sentry.Internal;
+using Sentry.Protocol;
using Sentry.Protocol.Envelopes;
//TODO: add XML docs
diff --git a/src/Sentry/SentryOptions.cs b/src/Sentry/SentryOptions.cs
index cc071ab387..73a6d25a6b 100644
--- a/src/Sentry/SentryOptions.cs
+++ b/src/Sentry/SentryOptions.cs
@@ -1,4 +1,3 @@
-using Sentry.Experimental;
using Sentry.Extensibility;
using Sentry.Http;
using Sentry.Infrastructure;
@@ -8,6 +7,7 @@
using Sentry.Internal.Http;
using Sentry.Internal.ScopeStack;
using Sentry.PlatformAbstractions;
+using Sentry.Protocol;
using static Sentry.SentryConstants;
#if HAS_DIAGNOSTIC_INTEGRATION
From 0f1d4a420319263bd4d45010b3c8a1b88ba6cada Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20P=C3=B6lz?=
<38893694+Flash0ver@users.noreply.github.com>
Date: Wed, 7 May 2025 18:58:59 +0200
Subject: [PATCH 010/101] feat(logs): change 'integer' from Int32 to Int64
---
.../Sentry.Samples.Console.Basic/Program.cs | 20 +++++++++++++------
src/Sentry/Protocol/SentryLog.cs | 8 ++++----
2 files changed, 18 insertions(+), 10 deletions(-)
diff --git a/samples/Sentry.Samples.Console.Basic/Program.cs b/samples/Sentry.Samples.Console.Basic/Program.cs
index dd1972ddeb..00f695442e 100644
--- a/samples/Sentry.Samples.Console.Basic/Program.cs
+++ b/samples/Sentry.Samples.Console.Basic/Program.cs
@@ -49,12 +49,20 @@
options.LogsSampleRate = 1.0f;
});
-SentrySdk.Logger.Trace("Hello, World!", null, log => log.SetAttribute("trace-key", "trace-value"));
-SentrySdk.Logger.Debug("Hello, .NET!", null, log => log.SetAttribute("debug-key", "debug-value"));
-SentrySdk.Logger.Info("Information", null, log => log.SetAttribute("info-key", "info-value"));
-SentrySdk.Logger.Warn("Warning with one {0}", ["parameter"], log => log.SetAttribute("warn-key", "warn-value"));
-SentrySdk.Logger.Error("Error with {0} {1}", [2, "parameters"], log => log.SetAttribute("error-key", "error-value"));
-SentrySdk.Logger.Fatal("Fatal {0} and {1}", [true, false], log => log.SetAttribute("fatal-key", "fatal-value"));
+var configureLog = static (SentryLog log) =>
+{
+ log.SetAttribute("string-attribute", "value");
+ log.SetAttribute("boolean-attribute", true);
+ log.SetAttribute("integer-attribute", long.MaxValue);
+ log.SetAttribute("double-attribute", double.MaxValue);
+};
+
+SentrySdk.Logger.Trace("Hello, World!", null, configureLog);
+SentrySdk.Logger.Debug("Hello, .NET!", null, configureLog);
+SentrySdk.Logger.Info("Information", null, configureLog);
+SentrySdk.Logger.Warn("Warning with one {0}", ["parameter"], configureLog);
+SentrySdk.Logger.Error("Error with {0} {1}", [2, "parameters"], configureLog);
+SentrySdk.Logger.Fatal("Fatal {0} and {1}", [true, false], configureLog);
await Task.Delay(TimeSpan.FromSeconds(5));
diff --git a/src/Sentry/Protocol/SentryLog.cs b/src/Sentry/Protocol/SentryLog.cs
index e6da56e6d6..f1f829b754 100644
--- a/src/Sentry/Protocol/SentryLog.cs
+++ b/src/Sentry/Protocol/SentryLog.cs
@@ -69,7 +69,7 @@ public void SetAttribute(string key, bool value)
_attributes[key] = new ValueTypePair(value, "boolean");
}
- public void SetAttribute(string key, int value)
+ public void SetAttribute(string key, long value)
{
_attributes ??= new Dictionary();
_attributes[key] = new ValueTypePair(value, "integer");
@@ -202,7 +202,7 @@ private static void WriteAttributeValue(Utf8JsonWriter writer, object value, str
}
else if (type == "integer")
{
- writer.WriteNumber("value", (int)value);
+ writer.WriteNumber("value", (long)value);
writer.WriteString("type", type);
}
else if (type == "double")
@@ -233,9 +233,9 @@ private static void WriteAttributeValue(Utf8JsonWriter writer, object value)
writer.WriteBoolean("value", boolean);
writer.WriteString("type", "boolean");
}
- else if (value is int int32)
+ else if (value is long int64)
{
- writer.WriteNumber("value", int32);
+ writer.WriteNumber("value", int64);
writer.WriteString("type", "integer");
}
else if (value is double float64)
From 8dec5d5223a3d5846ff21b79e84ef2c046755a83 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20P=C3=B6lz?=
<38893694+Flash0ver@users.noreply.github.com>
Date: Thu, 8 May 2025 12:50:32 +0200
Subject: [PATCH 011/101] ref(logs): refine API surface area
---
src/Sentry/Protocol/Envelopes/Envelope.cs | 2 +
src/Sentry/Protocol/Envelopes/EnvelopeItem.cs | 2 +
src/Sentry/Protocol/SentryLog.cs | 104 +++++++++++++++---
src/Sentry/Protocol/SentrySeverity.cs | 29 ++++-
src/Sentry/SentryLogger.cs | 54 +++++----
src/Sentry/SentryOptions.cs | 5 +-
src/Sentry/SentrySdk.cs | 12 +-
7 files changed, 163 insertions(+), 45 deletions(-)
diff --git a/src/Sentry/Protocol/Envelopes/Envelope.cs b/src/Sentry/Protocol/Envelopes/Envelope.cs
index 5f30d3539f..67b6daf55f 100644
--- a/src/Sentry/Protocol/Envelopes/Envelope.cs
+++ b/src/Sentry/Protocol/Envelopes/Envelope.cs
@@ -448,6 +448,8 @@ internal static Envelope FromClientReport(ClientReport clientReport)
[Experimental(DiagnosticId.ExperimentalFeature)]
internal static Envelope FromLog(SentryLog log)
{
+ //TODO: allow batching Sentry logs
+ //see https://github.com/getsentry/sentry-dotnet/issues/4132
var header = DefaultHeader;
var items = new[]
diff --git a/src/Sentry/Protocol/Envelopes/EnvelopeItem.cs b/src/Sentry/Protocol/Envelopes/EnvelopeItem.cs
index 3e05aa8017..0bf9ff94b9 100644
--- a/src/Sentry/Protocol/Envelopes/EnvelopeItem.cs
+++ b/src/Sentry/Protocol/Envelopes/EnvelopeItem.cs
@@ -375,6 +375,8 @@ internal static EnvelopeItem FromClientReport(ClientReport report)
[Experimental(DiagnosticId.ExperimentalFeature)]
internal static EnvelopeItem FromLog(SentryLog log)
{
+ //TODO: allow batching Sentry logs
+ //see https://github.com/getsentry/sentry-dotnet/issues/4132
var header = new Dictionary(3, StringComparer.Ordinal)
{
[TypeKey] = TypeValueLog,
diff --git a/src/Sentry/Protocol/SentryLog.cs b/src/Sentry/Protocol/SentryLog.cs
index f1f829b754..df69a818f6 100644
--- a/src/Sentry/Protocol/SentryLog.cs
+++ b/src/Sentry/Protocol/SentryLog.cs
@@ -2,10 +2,12 @@
using Sentry.Infrastructure;
using Sentry.Internal;
-#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
-
namespace Sentry.Protocol;
+///
+/// Represents the Sentry Log protocol.
+/// This API is experimental and it may change in the future.
+///
[Experimental(DiagnosticId.ExperimentalFeature)]
public sealed class SentryLog : ISentryJsonSerializable
{
@@ -21,18 +23,71 @@ internal SentryLog(DateTimeOffset timestamp, SentryId traceId, SentrySeverity le
Message = message;
}
+ ///
+ /// The timestamp of the log.
+ /// This API is experimental and it may change in the future.
+ ///
+ ///
+ /// Sent as seconds since the Unix epoch.
+ ///
+ [Experimental(DiagnosticId.ExperimentalFeature)]
public required DateTimeOffset Timestamp { get; init; }
+ ///
+ /// The trace id of the log.
+ /// This API is experimental and it may change in the future.
+ ///
+ [Experimental(DiagnosticId.ExperimentalFeature)]
public required SentryId TraceId { get; init; }
- private SentrySeverity Level
+ ///
+ /// The severity level of the log.
+ /// This API is experimental and it may change in the future.
+ ///
+ [Experimental(DiagnosticId.ExperimentalFeature)]
+ public required SentrySeverity Level
{
get => SentrySeverityExtensions.FromSeverityNumber(_severityNumber);
- set => _severityNumber = SentrySeverityExtensions.ToSeverityNumber(value);
+ init => _severityNumber = SentrySeverityExtensions.ToSeverityNumber(value);
+ }
+
+ ///
+ /// The severity number of the log.
+ /// This API is experimental and it may change in the future.
+ ///
+ [Experimental(DiagnosticId.ExperimentalFeature)]
+ public int SeverityNumber
+ {
+ get => _severityNumber;
+ set
+ {
+ SentrySeverityExtensions.ThrowIfOutOfRange(value);
+ _severityNumber = value;
+ }
}
+ ///
+ /// The formatted log message.
+ /// This API is experimental and it may change in the future.
+ ///
+ [Experimental(DiagnosticId.ExperimentalFeature)]
public required string Message { get; init; }
+ ///
+ /// A dictionary of key-value pairs of arbitrary data attached to the log.
+ /// This API is experimental and it may change in the future.
+ ///
+ ///
+ /// Attributes must also declare the type of the value.
+ /// The following types are supported:
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ [Experimental(DiagnosticId.ExperimentalFeature)]
public IReadOnlyDictionary Attributes
{
get
@@ -43,38 +98,58 @@ public IReadOnlyDictionary Attributes
}
}
+ ///
+ /// The parameterized template string.
+ /// This API is experimental and it may change in the future.
+ ///
+ [Experimental(DiagnosticId.ExperimentalFeature)]
public string? Template { get; init; }
+ ///
+ /// The parameters to the template string.
+ /// This API is experimental and it may change in the future.
+ ///
+ [Experimental(DiagnosticId.ExperimentalFeature)]
public object[]? Parameters { get; init; }
- public required int SeverityNumber
- {
- get => _severityNumber;
- set
- {
- SentrySeverityExtensions.ThrowIfOutOfRange(value);
- _severityNumber = value;
- }
- }
-
+ ///
+ /// Set a key-value pair of arbitrary data attached to the log.
+ /// This API is experimental and it may change in the future.
+ ///
+ [Experimental(DiagnosticId.ExperimentalFeature)]
public void SetAttribute(string key, string value)
{
_attributes ??= new Dictionary();
_attributes[key] = new ValueTypePair(value, "string");
}
+ ///
+ /// Set a key-value pair of arbitrary data attached to the log.
+ /// This API is experimental and it may change in the future.
+ ///
+ [Experimental(DiagnosticId.ExperimentalFeature)]
public void SetAttribute(string key, bool value)
{
_attributes ??= new Dictionary();
_attributes[key] = new ValueTypePair(value, "boolean");
}
+ ///
+ /// Set a key-value pair of arbitrary data attached to the log.
+ /// This API is experimental and it may change in the future.
+ ///
+ [Experimental(DiagnosticId.ExperimentalFeature)]
public void SetAttribute(string key, long value)
{
_attributes ??= new Dictionary();
_attributes[key] = new ValueTypePair(value, "integer");
}
+ ///
+ /// Set a key-value pair of arbitrary data attached to the log.
+ /// This API is experimental and it may change in the future.
+ ///
+ [Experimental(DiagnosticId.ExperimentalFeature)]
public void SetAttribute(string key, double value)
{
_attributes ??= new Dictionary();
@@ -113,6 +188,7 @@ internal void SetAttributes(IHub hub, IInternalScopeManager? scopeManager, Sentr
}
}
+ ///
public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
{
writer.WriteStartObject();
diff --git a/src/Sentry/Protocol/SentrySeverity.cs b/src/Sentry/Protocol/SentrySeverity.cs
index bf3b54daf4..e66b20c034 100644
--- a/src/Sentry/Protocol/SentrySeverity.cs
+++ b/src/Sentry/Protocol/SentrySeverity.cs
@@ -1,19 +1,38 @@
using Sentry.Infrastructure;
-#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
-
namespace Sentry.Protocol;
-//TODO: QUESTION: not sure about the name
-// this is a bit different to Sentry.SentryLevel and Sentry.BreadcrumbLevel
+///
+/// The severity of the structured log.
+/// This API is experimental and it may change in the future.
+///
+///
[Experimental(DiagnosticId.ExperimentalFeature)]
-internal enum SentrySeverity : short
+public enum SentrySeverity : short
{
+ ///
+ /// A fine-grained debugging event.
+ ///
Trace,
+ ///
+ /// A debugging event.
+ ///
Debug,
+ ///
+ /// An informational event.
+ ///
Info,
+ ///
+ /// A warning event.
+ ///
Warn,
+ ///
+ /// An error event.
+ ///
Error,
+ ///
+ /// A fatal error such as application or system crash.
+ ///
Fatal,
}
diff --git a/src/Sentry/SentryLogger.cs b/src/Sentry/SentryLogger.cs
index 637a886e52..ac89c06303 100644
--- a/src/Sentry/SentryLogger.cs
+++ b/src/Sentry/SentryLogger.cs
@@ -3,13 +3,11 @@
using Sentry.Protocol;
using Sentry.Protocol.Envelopes;
-//TODO: add XML docs
-#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
-
namespace Sentry;
///
/// Creates and sends logs to Sentry.
+/// This API is experimental and it may change in the future.
///
[Experimental(DiagnosticId.ExperimentalFeature)]
public sealed class SentryLogger
@@ -21,8 +19,10 @@ internal SentryLogger()
_randomValuesFactory = new SynchronizedRandomValuesFactory();
}
- //TODO: QUESTION: Trace vs LogTrace
- // Trace() is from the Sentry Logs feature specs. LogTrace() would be more .NET idiomatic
+ ///
+ /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled.
+ /// This API is experimental and it may change in the future.
+ ///
public void Trace(string template, object[]? parameters = null, Action? configureLog = null)
{
if (IsEnabled())
@@ -31,9 +31,11 @@ public void Trace(string template, object[]? parameters = null, Action
+ /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled.
+ /// This API is experimental and it may change in the future.
+ ///
+ [Experimental(DiagnosticId.ExperimentalFeature)]
public void Debug(string template, object[]? parameters = null, Action? configureLog = null)
{
if (IsEnabled())
@@ -42,6 +44,11 @@ public void Debug(string template, object[]? parameters = null, Action
+ /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled.
+ /// This API is experimental and it may change in the future.
+ ///
+ [Experimental(DiagnosticId.ExperimentalFeature)]
public void Info(string template, object[]? parameters = null, Action? configureLog = null)
{
if (IsEnabled())
@@ -50,8 +57,11 @@ public void Info(string template, object[]? parameters = null, Action
}
}
- //TODO: QUESTION: Warn vs Warning
- // Warn is from the Sentry Logs feature specs. Warning would be more .NET idiomatic
+ ///
+ /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled.
+ /// This API is experimental and it may change in the future.
+ ///
+ [Experimental(DiagnosticId.ExperimentalFeature)]
public void Warn(string template, object[]? parameters = null, Action? configureLog = null)
{
if (IsEnabled())
@@ -60,6 +70,11 @@ public void Warn(string template, object[]? parameters = null, Action
}
}
+ ///
+ /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled.
+ /// This API is experimental and it may change in the future.
+ ///
+ [Experimental(DiagnosticId.ExperimentalFeature)]
public void Error(string template, object[]? parameters = null, Action? configureLog = null)
{
if (IsEnabled())
@@ -68,6 +83,11 @@ public void Error(string template, object[]? parameters = null, Action
+ /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled.
+ /// This API is experimental and it may change in the future.
+ ///
+ [Experimental(DiagnosticId.ExperimentalFeature)]
public void Fatal(string template, object[]? parameters = null, Action? configureLog = null)
{
if (IsEnabled())
@@ -88,12 +108,6 @@ private bool IsEnabled()
return false;
}
- //TODO: consider ReadOnlySpan for TFMs where Span is available
- // or: utilize a custom [InterpolatedStringHandler] for modern TFMs
- // with which we may not be able to enforce on compile-time to only support string, boolean, integer, double
- // but we could have an Analyzer for that, indicating that Sentry does not support other types if used in the interpolated string
- // or: utilize a SourceGen, similar to the Microsoft.Extensions.Logging [LoggerMessage]
- // with which we could enforce on compile-time to only support string, boolean, integer, double
private void CaptureLog(SentrySeverity level, string template, object[]? parameters, Action? configureLog)
{
var timestamp = DateTimeOffset.UtcNow;
@@ -102,18 +116,14 @@ private void CaptureLog(SentrySeverity level, string template, object[]? paramet
if (hub.GetSentryOptions() is not { EnableLogs: true } options)
{
- //Logs disabled
return;
}
if (!_randomValuesFactory.NextBool(options.LogsSampleRate))
{
- //Log sampled
return;
}
- //process log (attach attributes)
-
var scopeManager = (hub as Hub)?.ScopeManager;
SentryId traceId;
if (hub.GetSpan() is { } span)
@@ -146,7 +156,8 @@ private void CaptureLog(SentrySeverity level, string template, object[]? paramet
}
catch (Exception e)
{
- //TODO: diagnostic log
+ //TODO: change to Diagnostic Logger (if enabled)
+ // see https://github.com/getsentry/sentry-dotnet/issues/4132
Console.WriteLine(e);
return;
}
@@ -154,6 +165,7 @@ private void CaptureLog(SentrySeverity level, string template, object[]? paramet
if (configuredLog is not null)
{
//TODO: enqueue in Batch-Processor / Background-Worker
+ // see https://github.com/getsentry/sentry-dotnet/issues/4132
_ = hub.CaptureEnvelope(Envelope.FromLog(configuredLog));
}
}
diff --git a/src/Sentry/SentryOptions.cs b/src/Sentry/SentryOptions.cs
index 73a6d25a6b..a86f8250ff 100644
--- a/src/Sentry/SentryOptions.cs
+++ b/src/Sentry/SentryOptions.cs
@@ -522,11 +522,11 @@ public void SetBeforeBreadcrumb(Func beforeBreadcrumb)
///
/// When set to , logs are sent to Sentry.
/// Defaults to .
+ /// This API is experimental and it may change in the future.
///
[Experimental(DiagnosticId.ExperimentalFeature)]
public bool EnableLogs { get; set; } = false;
- [Experimental(DiagnosticId.ExperimentalFeature)]
private Func? _beforeSendLog;
[Experimental(DiagnosticId.ExperimentalFeature)]
@@ -534,6 +534,7 @@ public void SetBeforeBreadcrumb(Func beforeBreadcrumb)
///
/// Sets a callback function to be invoked before sending the log to Sentry.
+ /// This API is experimental and it may change in the future.
///
///
/// It can be used to modify the log object before being sent to Sentry.
@@ -545,12 +546,12 @@ public void SetBeforeSendLog(Func beforeSendLog)
_beforeSendLog = beforeSendLog;
}
- [Experimental(DiagnosticId.ExperimentalFeature)]
private float _logsSampleRate = 1.0f;
///
/// A between 0.0f and 1.0f that represents the probability that a log will be sent to Sentry.
/// Defaults to 1.0.
+ /// This API is experimental and it may change in the future.
///
[Experimental(DiagnosticId.ExperimentalFeature)]
public float LogsSampleRate
diff --git a/src/Sentry/SentrySdk.cs b/src/Sentry/SentrySdk.cs
index 22f8d8b943..952e4d6e68 100644
--- a/src/Sentry/SentrySdk.cs
+++ b/src/Sentry/SentrySdk.cs
@@ -282,10 +282,16 @@ public void Dispose()
///
/// Creates and sends logs to Sentry.
+ /// This API is experimental and it may change in the future.
///
- //TODO: add to IHub or ISentryClient
- // adding to interfaces is breaking, perhaps via a DIM but what about netstandard2.0 runtimes
- // or are these interfaces intended to be extended as user code is not meant to implement them
+ ///
+ /// Available options:
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
[Experimental(DiagnosticId.ExperimentalFeature)]
public static SentryLogger Logger { get; } = new SentryLogger();
From a664f7efd5a433456ad68ab8ff1f5912559a9ef6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20P=C3=B6lz?=
<38893694+Flash0ver@users.noreply.github.com>
Date: Thu, 8 May 2025 17:55:34 +0200
Subject: [PATCH 012/101] ref(logs): match SeverityLevel to OTel spec
---
.../Sentry.Samples.Console.Basic/Program.cs | 2 +-
src/Sentry/Protocol/SentryLog.cs | 33 +++-----
src/Sentry/Protocol/SentrySeverity.cs | 76 ++++++++-----------
3 files changed, 45 insertions(+), 66 deletions(-)
diff --git a/samples/Sentry.Samples.Console.Basic/Program.cs b/samples/Sentry.Samples.Console.Basic/Program.cs
index 00f695442e..74182feb65 100644
--- a/samples/Sentry.Samples.Console.Basic/Program.cs
+++ b/samples/Sentry.Samples.Console.Basic/Program.cs
@@ -44,7 +44,7 @@
return null;
}
- return log.SeverityNumber is >= 17 and <= 20 ? log : null;
+ return log.Level is SentrySeverity.Error ? log : null;
});
options.LogsSampleRate = 1.0f;
});
diff --git a/src/Sentry/Protocol/SentryLog.cs b/src/Sentry/Protocol/SentryLog.cs
index df69a818f6..f86deec36d 100644
--- a/src/Sentry/Protocol/SentryLog.cs
+++ b/src/Sentry/Protocol/SentryLog.cs
@@ -12,7 +12,7 @@ namespace Sentry.Protocol;
public sealed class SentryLog : ISentryJsonSerializable
{
private Dictionary? _attributes;
- private int _severityNumber = -1;
+ private readonly SentrySeverity _severity;
[SetsRequiredMembers]
internal SentryLog(DateTimeOffset timestamp, SentryId traceId, SentrySeverity level, string message)
@@ -47,22 +47,11 @@ internal SentryLog(DateTimeOffset timestamp, SentryId traceId, SentrySeverity le
[Experimental(DiagnosticId.ExperimentalFeature)]
public required SentrySeverity Level
{
- get => SentrySeverityExtensions.FromSeverityNumber(_severityNumber);
- init => _severityNumber = SentrySeverityExtensions.ToSeverityNumber(value);
- }
-
- ///
- /// The severity number of the log.
- /// This API is experimental and it may change in the future.
- ///
- [Experimental(DiagnosticId.ExperimentalFeature)]
- public int SeverityNumber
- {
- get => _severityNumber;
- set
+ get => _severity;
+ init
{
SentrySeverityExtensions.ThrowIfOutOfRange(value);
- _severityNumber = value;
+ _severity = value;
}
}
@@ -197,7 +186,14 @@ public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
writer.WriteNumber("timestamp", Timestamp.ToUnixTimeSeconds());
writer.WriteString("trace_id", TraceId);
- writer.WriteString("level", Level.ToLogString());
+
+ var (severityText, severityNumber) = Level.ToSeverityTextAndOptionalNumber();
+ writer.WriteString("level", severityText);
+ if (severityNumber.HasValue)
+ {
+ writer.WriteNumber("severity_number", severityNumber.Value);
+ }
+
writer.WriteString("body", Message);
writer.WritePropertyName("attributes");
@@ -226,11 +222,6 @@ public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
writer.WriteEndObject();
- if (SeverityNumber != -1)
- {
- writer.WriteNumber("severity_number", SeverityNumber);
- }
-
writer.WriteEndObject();
writer.WriteEndArray();
writer.WriteEndObject();
diff --git a/src/Sentry/Protocol/SentrySeverity.cs b/src/Sentry/Protocol/SentrySeverity.cs
index e66b20c034..56727b8ee1 100644
--- a/src/Sentry/Protocol/SentrySeverity.cs
+++ b/src/Sentry/Protocol/SentrySeverity.cs
@@ -8,86 +8,74 @@ namespace Sentry.Protocol;
///
///
[Experimental(DiagnosticId.ExperimentalFeature)]
-public enum SentrySeverity : short
+public enum SentrySeverity
{
///
/// A fine-grained debugging event.
///
- Trace,
+ Trace = 1,
///
/// A debugging event.
///
- Debug,
+ Debug = 5,
///
/// An informational event.
///
- Info,
+ Info = 9,
///
/// A warning event.
///
- Warn,
+ Warn = 13,
///
/// An error event.
///
- Error,
+ Error = 17,
///
/// A fatal error such as application or system crash.
///
- Fatal,
+ Fatal = 21,
}
[Experimental(DiagnosticId.ExperimentalFeature)]
internal static class SentrySeverityExtensions
{
- internal static string ToLogString(this SentrySeverity severity)
+ internal static (string, int?) ToSeverityTextAndOptionalNumber(this SentrySeverity severity)
{
- return severity switch
+ return (int)severity switch
{
- SentrySeverity.Trace => "trace",
- SentrySeverity.Debug => "debug",
- SentrySeverity.Info => "info",
- SentrySeverity.Warn => "warn",
- SentrySeverity.Error => "error",
- SentrySeverity.Fatal => "fatal",
- _ => throw new ArgumentOutOfRangeException(nameof(severity), severity, null),
+ 1 => ("trace", null),
+ >= 2 and <= 4 => ("trace", (int)severity),
+ 5 => ("debug", null),
+ >= 6 and <= 8 => ("debug", (int)severity),
+ 9 => ("info", null),
+ >= 10 and <= 12 => ("info", (int)severity),
+ 13 => ("warn", null),
+ >= 14 and <= 16 => ("warn", (int)severity),
+ 17 => ("error", null),
+ >= 18 and <= 20 => ("error", (int)severity),
+ 21 => ("fatal", null),
+ >= 22 and <= 24 => ("fatal", (int)severity),
+ _ => ThrowOutOfRange<(string, int?)>(severity, nameof(severity)),
};
}
- internal static SentrySeverity FromSeverityNumber(int severityNumber)
+ internal static void ThrowIfOutOfRange(SentrySeverity severity, [CallerArgumentExpression(nameof(severity))] string? paramName = null)
{
- ThrowIfOutOfRange(severityNumber);
-
- return severityNumber switch
+ if ((int)severity is < 1 or > 24)
{
- >= 1 and <= 4 => SentrySeverity.Trace,
- >= 5 and <= 8 => SentrySeverity.Debug,
- >= 9 and <= 12 => SentrySeverity.Info,
- >= 13 and <= 16 => SentrySeverity.Warn,
- >= 17 and <= 20 => SentrySeverity.Error,
- >= 21 and <= 24 => SentrySeverity.Fatal,
- _ => throw new UnreachableException(),
- };
+ ThrowOutOfRange(severity, paramName);
+ }
}
- internal static int ToSeverityNumber(SentrySeverity severity)
+ [DoesNotReturn]
+ private static void ThrowOutOfRange(SentrySeverity severity, string? paramName)
{
- return severity switch
- {
- SentrySeverity.Trace => 1,
- SentrySeverity.Debug => 5,
- SentrySeverity.Info => 9,
- SentrySeverity.Warn => 13,
- SentrySeverity.Error => 17,
- SentrySeverity.Fatal => 21,
- _ => throw new ArgumentOutOfRangeException(nameof(severity), severity, null)
- };
+ throw new ArgumentOutOfRangeException(paramName, severity, "Severity must be between 1 (inclusive) and 24 (inclusive).");
}
- internal static void ThrowIfOutOfRange(int severityNumber)
+ [DoesNotReturn]
+ private static T ThrowOutOfRange(SentrySeverity severity, string? paramName)
{
- if (severityNumber is < 1 or > 24)
- {
- throw new ArgumentOutOfRangeException(nameof(severityNumber), severityNumber, "SeverityNumber must be between 1 (inclusive) and 24 (inclusive).");
- }
+ throw new ArgumentOutOfRangeException(paramName, severity, "Severity must be between 1 (inclusive) and 24 (inclusive).");
}
}
From 96693d09ee5def433fe77eba171dd412176ae4dd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20P=C3=B6lz?=
<38893694+Flash0ver@users.noreply.github.com>
Date: Thu, 8 May 2025 18:26:31 +0200
Subject: [PATCH 013/101] ref(logs): rename SentrySeverity to LogSeverityLevel
---
.../Sentry.Samples.Console.Basic/Program.cs | 2 +-
...{SentrySeverity.cs => LogSeverityLevel.cs} | 36 +++++++++----------
src/Sentry/Protocol/SentryLog.cs | 12 +++----
src/Sentry/SentryLogger.cs | 26 +++++++-------
4 files changed, 38 insertions(+), 38 deletions(-)
rename src/Sentry/Protocol/{SentrySeverity.cs => LogSeverityLevel.cs} (51%)
diff --git a/samples/Sentry.Samples.Console.Basic/Program.cs b/samples/Sentry.Samples.Console.Basic/Program.cs
index 74182feb65..4f99dab93c 100644
--- a/samples/Sentry.Samples.Console.Basic/Program.cs
+++ b/samples/Sentry.Samples.Console.Basic/Program.cs
@@ -44,7 +44,7 @@
return null;
}
- return log.Level is SentrySeverity.Error ? log : null;
+ return log.Level is LogSeverityLevel.Error ? log : null;
});
options.LogsSampleRate = 1.0f;
});
diff --git a/src/Sentry/Protocol/SentrySeverity.cs b/src/Sentry/Protocol/LogSeverityLevel.cs
similarity index 51%
rename from src/Sentry/Protocol/SentrySeverity.cs
rename to src/Sentry/Protocol/LogSeverityLevel.cs
index 56727b8ee1..2642d7e850 100644
--- a/src/Sentry/Protocol/SentrySeverity.cs
+++ b/src/Sentry/Protocol/LogSeverityLevel.cs
@@ -8,7 +8,7 @@ namespace Sentry.Protocol;
///
///
[Experimental(DiagnosticId.ExperimentalFeature)]
-public enum SentrySeverity
+public enum LogSeverityLevel
{
///
/// A fine-grained debugging event.
@@ -37,45 +37,45 @@ public enum SentrySeverity
}
[Experimental(DiagnosticId.ExperimentalFeature)]
-internal static class SentrySeverityExtensions
+internal static class LogSeverityLevelExtensions
{
- internal static (string, int?) ToSeverityTextAndOptionalNumber(this SentrySeverity severity)
+ internal static (string, int?) ToSeverityTextAndOptionalNumber(this LogSeverityLevel level)
{
- return (int)severity switch
+ return (int)level switch
{
1 => ("trace", null),
- >= 2 and <= 4 => ("trace", (int)severity),
+ >= 2 and <= 4 => ("trace", (int)level),
5 => ("debug", null),
- >= 6 and <= 8 => ("debug", (int)severity),
+ >= 6 and <= 8 => ("debug", (int)level),
9 => ("info", null),
- >= 10 and <= 12 => ("info", (int)severity),
+ >= 10 and <= 12 => ("info", (int)level),
13 => ("warn", null),
- >= 14 and <= 16 => ("warn", (int)severity),
+ >= 14 and <= 16 => ("warn", (int)level),
17 => ("error", null),
- >= 18 and <= 20 => ("error", (int)severity),
+ >= 18 and <= 20 => ("error", (int)level),
21 => ("fatal", null),
- >= 22 and <= 24 => ("fatal", (int)severity),
- _ => ThrowOutOfRange<(string, int?)>(severity, nameof(severity)),
+ >= 22 and <= 24 => ("fatal", (int)level),
+ _ => ThrowOutOfRange<(string, int?)>(level, nameof(level)),
};
}
- internal static void ThrowIfOutOfRange(SentrySeverity severity, [CallerArgumentExpression(nameof(severity))] string? paramName = null)
+ internal static void ThrowIfOutOfRange(LogSeverityLevel level, [CallerArgumentExpression(nameof(level))] string? paramName = null)
{
- if ((int)severity is < 1 or > 24)
+ if ((int)level is < 1 or > 24)
{
- ThrowOutOfRange(severity, paramName);
+ ThrowOutOfRange(level, paramName);
}
}
[DoesNotReturn]
- private static void ThrowOutOfRange(SentrySeverity severity, string? paramName)
+ private static void ThrowOutOfRange(LogSeverityLevel level, string? paramName)
{
- throw new ArgumentOutOfRangeException(paramName, severity, "Severity must be between 1 (inclusive) and 24 (inclusive).");
+ throw new ArgumentOutOfRangeException(paramName, level, "Severity must be between 1 (inclusive) and 24 (inclusive).");
}
[DoesNotReturn]
- private static T ThrowOutOfRange(SentrySeverity severity, string? paramName)
+ private static T ThrowOutOfRange(LogSeverityLevel level, string? paramName)
{
- throw new ArgumentOutOfRangeException(paramName, severity, "Severity must be between 1 (inclusive) and 24 (inclusive).");
+ throw new ArgumentOutOfRangeException(paramName, level, "Severity must be between 1 (inclusive) and 24 (inclusive).");
}
}
diff --git a/src/Sentry/Protocol/SentryLog.cs b/src/Sentry/Protocol/SentryLog.cs
index f86deec36d..66e1a63cb5 100644
--- a/src/Sentry/Protocol/SentryLog.cs
+++ b/src/Sentry/Protocol/SentryLog.cs
@@ -12,10 +12,10 @@ namespace Sentry.Protocol;
public sealed class SentryLog : ISentryJsonSerializable
{
private Dictionary? _attributes;
- private readonly SentrySeverity _severity;
+ private readonly LogSeverityLevel _level;
[SetsRequiredMembers]
- internal SentryLog(DateTimeOffset timestamp, SentryId traceId, SentrySeverity level, string message)
+ internal SentryLog(DateTimeOffset timestamp, SentryId traceId, LogSeverityLevel level, string message)
{
Timestamp = timestamp;
TraceId = traceId;
@@ -45,13 +45,13 @@ internal SentryLog(DateTimeOffset timestamp, SentryId traceId, SentrySeverity le
/// This API is experimental and it may change in the future.
///
[Experimental(DiagnosticId.ExperimentalFeature)]
- public required SentrySeverity Level
+ public required LogSeverityLevel Level
{
- get => _severity;
+ get => _level;
init
{
- SentrySeverityExtensions.ThrowIfOutOfRange(value);
- _severity = value;
+ LogSeverityLevelExtensions.ThrowIfOutOfRange(value);
+ _level = value;
}
}
diff --git a/src/Sentry/SentryLogger.cs b/src/Sentry/SentryLogger.cs
index ac89c06303..a1514927ca 100644
--- a/src/Sentry/SentryLogger.cs
+++ b/src/Sentry/SentryLogger.cs
@@ -20,19 +20,19 @@ internal SentryLogger()
}
///
- /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled.
+ /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled.
/// This API is experimental and it may change in the future.
///
public void Trace(string template, object[]? parameters = null, Action? configureLog = null)
{
if (IsEnabled())
{
- CaptureLog(SentrySeverity.Trace, template, parameters, configureLog);
+ CaptureLog(LogSeverityLevel.Trace, template, parameters, configureLog);
}
}
///
- /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled.
+ /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled.
/// This API is experimental and it may change in the future.
///
[Experimental(DiagnosticId.ExperimentalFeature)]
@@ -40,12 +40,12 @@ public void Debug(string template, object[]? parameters = null, Action
- /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled.
+ /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled.
/// This API is experimental and it may change in the future.
///
[Experimental(DiagnosticId.ExperimentalFeature)]
@@ -53,12 +53,12 @@ public void Info(string template, object[]? parameters = null, Action
{
if (IsEnabled())
{
- CaptureLog(SentrySeverity.Info, template, parameters, configureLog);
+ CaptureLog(LogSeverityLevel.Info, template, parameters, configureLog);
}
}
///
- /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled.
+ /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled.
/// This API is experimental and it may change in the future.
///
[Experimental(DiagnosticId.ExperimentalFeature)]
@@ -66,12 +66,12 @@ public void Warn(string template, object[]? parameters = null, Action
{
if (IsEnabled())
{
- CaptureLog(SentrySeverity.Warn, template, parameters, configureLog);
+ CaptureLog(LogSeverityLevel.Warn, template, parameters, configureLog);
}
}
///
- /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled.
+ /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled.
/// This API is experimental and it may change in the future.
///
[Experimental(DiagnosticId.ExperimentalFeature)]
@@ -79,12 +79,12 @@ public void Error(string template, object[]? parameters = null, Action
- /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled.
+ /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled.
/// This API is experimental and it may change in the future.
///
[Experimental(DiagnosticId.ExperimentalFeature)]
@@ -92,7 +92,7 @@ public void Fatal(string template, object[]? parameters = null, Action? configureLog)
+ private void CaptureLog(LogSeverityLevel level, string template, object[]? parameters, Action? configureLog)
{
var timestamp = DateTimeOffset.UtcNow;
From 83964cfc78cfa73d026bebc99aa31620beb1ff2c Mon Sep 17 00:00:00 2001
From: Sentry Github Bot
Date: Thu, 8 May 2025 16:39:46 +0000
Subject: [PATCH 014/101] Format code
---
src/Sentry/Protocol/SentryLog.cs | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/Sentry/Protocol/SentryLog.cs b/src/Sentry/Protocol/SentryLog.cs
index 66e1a63cb5..a6bb4aedf6 100644
--- a/src/Sentry/Protocol/SentryLog.cs
+++ b/src/Sentry/Protocol/SentryLog.cs
@@ -70,10 +70,10 @@ public required LogSeverityLevel Level
/// Attributes must also declare the type of the value.
/// The following types are supported:
///
- ///
- ///
- ///
- ///
+ ///
+ ///
+ ///
+ ///
///
///
[Experimental(DiagnosticId.ExperimentalFeature)]
From dadc69b3aecfb8f556e8e49d71461d7490e52421 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20P=C3=B6lz?=
<38893694+Flash0ver@users.noreply.github.com>
Date: Fri, 9 May 2025 12:11:58 +0200
Subject: [PATCH 015/101] ref(logs): hide underlying Dictionary`2 for
Attributes
---
.../Sentry.Samples.Console.Basic/Program.cs | 3 +-
src/Sentry/Protocol/SentryLog.cs | 117 +++++++++++++-----
2 files changed, 86 insertions(+), 34 deletions(-)
diff --git a/samples/Sentry.Samples.Console.Basic/Program.cs b/samples/Sentry.Samples.Console.Basic/Program.cs
index 4f99dab93c..17cf00ab21 100644
--- a/samples/Sentry.Samples.Console.Basic/Program.cs
+++ b/samples/Sentry.Samples.Console.Basic/Program.cs
@@ -38,8 +38,7 @@
options.EnableLogs = true;
options.SetBeforeSendLog(static (SentryLog log) =>
{
- //TODO: this feels a bit off ... perhaps a "TryGet{Type}Attribute" method group could help here instead of exposing the boxing object-TValue-based Dictionary`2
- if (log.Attributes.TryGetValue("plan.type", out var attribute) && attribute is "enterprise")
+ if (log.TryGetAttribute("plan.type", out string? attribute) && attribute == "enterprise")
{
return null;
}
diff --git a/src/Sentry/Protocol/SentryLog.cs b/src/Sentry/Protocol/SentryLog.cs
index a6bb4aedf6..e3563ddf64 100644
--- a/src/Sentry/Protocol/SentryLog.cs
+++ b/src/Sentry/Protocol/SentryLog.cs
@@ -11,7 +11,7 @@ namespace Sentry.Protocol;
[Experimental(DiagnosticId.ExperimentalFeature)]
public sealed class SentryLog : ISentryJsonSerializable
{
- private Dictionary? _attributes;
+ private readonly Dictionary _attributes;
private readonly LogSeverityLevel _level;
[SetsRequiredMembers]
@@ -21,6 +21,7 @@ internal SentryLog(DateTimeOffset timestamp, SentryId traceId, LogSeverityLevel
TraceId = traceId;
Level = level;
Message = message;
+ _attributes = new Dictionary(7);
}
///
@@ -63,85 +64,140 @@ public required LogSeverityLevel Level
public required string Message { get; init; }
///
- /// A dictionary of key-value pairs of arbitrary data attached to the log.
+ /// The parameterized template string.
+ /// This API is experimental and it may change in the future.
+ ///
+ [Experimental(DiagnosticId.ExperimentalFeature)]
+ public string? Template { get; init; }
+
+ ///
+ /// The parameters to the template string.
+ /// This API is experimental and it may change in the future.
+ ///
+ [Experimental(DiagnosticId.ExperimentalFeature)]
+ public object[]? Parameters { get; init; }
+
+ ///
+ /// Gets the attribute value associated with the specified key when of type .
/// This API is experimental and it may change in the future.
///
///
- /// Attributes must also declare the type of the value.
- /// The following types are supported:
- ///
- ///
- ///
- ///
- ///
- ///
+ /// Returns if the contains an attribute with the specified key of type .
+ /// Otherwise .
///
[Experimental(DiagnosticId.ExperimentalFeature)]
- public IReadOnlyDictionary Attributes
+ public bool TryGetAttribute(string key, [NotNullWhen(true)] out string? value)
{
- get
+ if (_attributes.TryGetValue(key, out var attribute) && attribute.Type == "string")
{
- return _attributes is null
- ? []
- : _attributes.ToDictionary(static item => item.Key, item => item.Value.Value);
+ value = (string)attribute.Value;
+ return true;
}
+
+ value = null;
+ return false;
}
///
- /// The parameterized template string.
+ /// Gets the attribute value associated with the specified key when of type .
/// This API is experimental and it may change in the future.
///
+ ///
+ /// Returns if the contains an attribute with the specified key of type .
+ /// Otherwise .
+ ///
[Experimental(DiagnosticId.ExperimentalFeature)]
- public string? Template { get; init; }
+ public bool TryGetAttribute(string key, out bool value)
+ {
+ if (_attributes.TryGetValue(key, out var attribute) && attribute.Type == "boolean")
+ {
+ value = (bool)attribute.Value;
+ return true;
+ }
+
+ value = false;
+ return false;
+ }
///
- /// The parameters to the template string.
+ /// Gets the attribute value associated with the specified key when of type .
/// This API is experimental and it may change in the future.
///
+ ///
+ /// Returns if the contains an attribute with the specified key of type .
+ /// Otherwise .
+ ///
[Experimental(DiagnosticId.ExperimentalFeature)]
- public object[]? Parameters { get; init; }
+ public bool TryGetAttribute(string key, out long value)
+ {
+ if (_attributes.TryGetValue(key, out var attribute) && attribute.Type == "integer")
+ {
+ value = (long)attribute.Value;
+ return true;
+ }
+
+ value = 0L;
+ return false;
+ }
///
- /// Set a key-value pair of arbitrary data attached to the log.
+ /// Gets the attribute value associated with the specified key when of type .
+ /// This API is experimental and it may change in the future.
+ ///
+ ///
+ /// Returns if the contains an attribute with the specified key of type .
+ /// Otherwise .
+ ///
+ [Experimental(DiagnosticId.ExperimentalFeature)]
+ public bool TryGetAttribute(string key, out double value)
+ {
+ if (_attributes.TryGetValue(key, out var attribute) && attribute.Type == "double")
+ {
+ value = (double)attribute.Value;
+ return true;
+ }
+
+ value = 0.0;
+ return false;
+ }
+
+ ///
+ /// Set a key-value pair of data attached to the log.
/// This API is experimental and it may change in the future.
///
[Experimental(DiagnosticId.ExperimentalFeature)]
public void SetAttribute(string key, string value)
{
- _attributes ??= new Dictionary();
_attributes[key] = new ValueTypePair(value, "string");
}
///
- /// Set a key-value pair of arbitrary data attached to the log.
+ /// Set a key-value pair of data attached to the log.
/// This API is experimental and it may change in the future.
///
[Experimental(DiagnosticId.ExperimentalFeature)]
public void SetAttribute(string key, bool value)
{
- _attributes ??= new Dictionary();
_attributes[key] = new ValueTypePair(value, "boolean");
}
///
- /// Set a key-value pair of arbitrary data attached to the log.
+ /// Set a key-value pair of data attached to the log.
/// This API is experimental and it may change in the future.
///
[Experimental(DiagnosticId.ExperimentalFeature)]
public void SetAttribute(string key, long value)
{
- _attributes ??= new Dictionary();
_attributes[key] = new ValueTypePair(value, "integer");
}
///
- /// Set a key-value pair of arbitrary data attached to the log.
+ /// Set a key-value pair of data attached to the log.
/// This API is experimental and it may change in the future.
///
[Experimental(DiagnosticId.ExperimentalFeature)]
public void SetAttribute(string key, double value)
{
- _attributes ??= new Dictionary();
_attributes[key] = new ValueTypePair(value, "double");
}
@@ -212,12 +268,9 @@ public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
}
}
- if (_attributes is not null)
+ foreach (var attribute in _attributes)
{
- foreach (var attribute in _attributes)
- {
- WriteAttribute(writer, attribute.Key, attribute.Value);
- }
+ WriteAttribute(writer, attribute.Key, attribute.Value);
}
writer.WriteEndObject();
From c91cdde22d3ebb671f6a6dd87b21f84aac126995 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20P=C3=B6lz?=
<38893694+Flash0ver@users.noreply.github.com>
Date: Fri, 9 May 2025 13:28:53 +0200
Subject: [PATCH 016/101] ref(logs): restructure attributes
---
src/Sentry/Protocol/SentryAttribute.cs | 101 +++++++++++++++++++++
src/Sentry/Protocol/SentryLog.cs | 119 +++----------------------
src/Sentry/SentryLogger.cs | 2 +-
3 files changed, 113 insertions(+), 109 deletions(-)
create mode 100644 src/Sentry/Protocol/SentryAttribute.cs
diff --git a/src/Sentry/Protocol/SentryAttribute.cs b/src/Sentry/Protocol/SentryAttribute.cs
new file mode 100644
index 0000000000..6e5845e6dc
--- /dev/null
+++ b/src/Sentry/Protocol/SentryAttribute.cs
@@ -0,0 +1,101 @@
+namespace Sentry.Protocol;
+
+internal readonly struct SentryAttribute
+{
+ public SentryAttribute(object value, string type)
+ {
+ Value = value;
+ Type = type;
+ }
+
+ public object Value { get; }
+ public string Type { get; }
+}
+
+internal static class SentryAttributeSerializer
+{
+ internal static void WriteAttribute(Utf8JsonWriter writer, string propertyName, SentryAttribute attribute)
+ {
+ Debug.Assert(attribute.Type is not null);
+ writer.WritePropertyName(propertyName);
+ WriteAttributeValue(writer, attribute.Value, attribute.Type);
+ }
+
+ internal static void WriteAttribute(Utf8JsonWriter writer, string propertyName, object value, string type)
+ {
+ writer.WritePropertyName(propertyName);
+ WriteAttributeValue(writer, value, type);
+ }
+
+ internal static void WriteAttribute(Utf8JsonWriter writer, string propertyName, object value)
+ {
+ writer.WritePropertyName(propertyName);
+ WriteAttributeValue(writer, value);
+ }
+
+ private static void WriteAttributeValue(Utf8JsonWriter writer, object value, string type)
+ {
+ writer.WriteStartObject();
+
+ if (type == "string")
+ {
+ writer.WriteString("value", (string)value);
+ writer.WriteString("type", type);
+ }
+ else if (type == "boolean")
+ {
+ writer.WriteBoolean("value", (bool)value);
+ writer.WriteString("type", type);
+ }
+ else if (type == "integer")
+ {
+ writer.WriteNumber("value", (long)value);
+ writer.WriteString("type", type);
+ }
+ else if (type == "double")
+ {
+ writer.WriteNumber("value", (double)value);
+ writer.WriteString("type", type);
+ }
+ else
+ {
+ writer.WriteString("value", value.ToString());
+ writer.WriteString("type", "string");
+ }
+
+ writer.WriteEndObject();
+ }
+
+ private static void WriteAttributeValue(Utf8JsonWriter writer, object value)
+ {
+ writer.WriteStartObject();
+
+ if (value is string str)
+ {
+ writer.WriteString("value", str);
+ writer.WriteString("type", "string");
+ }
+ else if (value is bool boolean)
+ {
+ writer.WriteBoolean("value", boolean);
+ writer.WriteString("type", "boolean");
+ }
+ else if (value is long int64)
+ {
+ writer.WriteNumber("value", int64);
+ writer.WriteString("type", "integer");
+ }
+ else if (value is double float64)
+ {
+ writer.WriteNumber("value", float64);
+ writer.WriteString("type", "double");
+ }
+ else
+ {
+ writer.WriteString("value", value.ToString());
+ writer.WriteString("type", "string");
+ }
+
+ writer.WriteEndObject();
+ }
+}
diff --git a/src/Sentry/Protocol/SentryLog.cs b/src/Sentry/Protocol/SentryLog.cs
index e3563ddf64..a01d465e47 100644
--- a/src/Sentry/Protocol/SentryLog.cs
+++ b/src/Sentry/Protocol/SentryLog.cs
@@ -11,7 +11,7 @@ namespace Sentry.Protocol;
[Experimental(DiagnosticId.ExperimentalFeature)]
public sealed class SentryLog : ISentryJsonSerializable
{
- private readonly Dictionary _attributes;
+ private readonly Dictionary _attributes;
private readonly LogSeverityLevel _level;
[SetsRequiredMembers]
@@ -21,7 +21,7 @@ internal SentryLog(DateTimeOffset timestamp, SentryId traceId, LogSeverityLevel
TraceId = traceId;
Level = level;
Message = message;
- _attributes = new Dictionary(7);
+ _attributes = new Dictionary(7);
}
///
@@ -75,7 +75,7 @@ public required LogSeverityLevel Level
/// This API is experimental and it may change in the future.
///
[Experimental(DiagnosticId.ExperimentalFeature)]
- public object[]? Parameters { get; init; }
+ public ImmutableArray
[Experimental(DiagnosticId.ExperimentalFeature)]
@@ -72,12 +72,12 @@ public void LogWarning(string template, object[]? parameters = null, Action
- /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled.
+ /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled.
/// This API is experimental and it may change in the future.
///
[Experimental(DiagnosticId.ExperimentalFeature)]
@@ -85,12 +85,12 @@ public void LogError(string template, object[]? parameters = null, Action
- /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled.
+ /// Creates and sends a structured log to Sentry, with severity , when enabled and sampled.
/// This API is experimental and it may change in the future.
///
[Experimental(DiagnosticId.ExperimentalFeature)]
@@ -98,7 +98,7 @@ public void LogFatal(string template, object[]? parameters = null, Action? configureLog)
+ private void CaptureLog(SentryLogLevel level, string template, object[]? parameters, Action? configureLog)
{
var timestamp = _clock.GetUtcNow();
From dcc0ec1711a9a6728ceac58423dad67259515146 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20P=C3=B6lz?=
<38893694+Flash0ver@users.noreply.github.com>
Date: Tue, 13 May 2025 10:43:23 +0200
Subject: [PATCH 023/101] ref(logs): re-rename new logger type
---
src/Sentry/SentrySdk.cs | 2 +-
.../{SentrySdkLogger.cs => SentryStructuredLogger.cs} | 6 +++---
2 files changed, 4 insertions(+), 4 deletions(-)
rename src/Sentry/{SentrySdkLogger.cs => SentryStructuredLogger.cs} (97%)
diff --git a/src/Sentry/SentrySdk.cs b/src/Sentry/SentrySdk.cs
index 7f88448ea6..8181ee79df 100644
--- a/src/Sentry/SentrySdk.cs
+++ b/src/Sentry/SentrySdk.cs
@@ -292,7 +292,7 @@ public void Dispose()
///
///
[Experimental(DiagnosticId.ExperimentalFeature)]
- public static SentrySdkLogger Logger { get; } = new SentrySdkLogger();
+ public static SentryStructuredLogger Logger { get; } = new SentryStructuredLogger();
///
/// Creates a new scope that will terminate when disposed.
diff --git a/src/Sentry/SentrySdkLogger.cs b/src/Sentry/SentryStructuredLogger.cs
similarity index 97%
rename from src/Sentry/SentrySdkLogger.cs
rename to src/Sentry/SentryStructuredLogger.cs
index 144e9d90c6..55b7ec439d 100644
--- a/src/Sentry/SentrySdkLogger.cs
+++ b/src/Sentry/SentryStructuredLogger.cs
@@ -11,16 +11,16 @@ namespace Sentry;
/// This API is experimental and it may change in the future.
///
[Experimental(DiagnosticId.ExperimentalFeature)]
-public sealed class SentrySdkLogger
+public sealed class SentryStructuredLogger
{
private readonly ISystemClock _clock;
- internal SentrySdkLogger()
+ internal SentryStructuredLogger()
: this(SystemClock.Clock)
{
}
- internal SentrySdkLogger(ISystemClock clock)
+ internal SentryStructuredLogger(ISystemClock clock)
{
_clock = clock;
}
From 58dce74cac400de6d1c1806315a4d067da174836 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20P=C3=B6lz?=
<38893694+Flash0ver@users.noreply.github.com>
Date: Wed, 14 May 2025 09:27:20 +0200
Subject: [PATCH 024/101] ref(logs): move Logger instances to Hubs
---
src/Sentry/Extensibility/DisabledHub.cs | 9 +++
src/Sentry/Extensibility/HubAdapter.cs | 7 ++
src/Sentry/IHub.cs | 16 +++++
src/Sentry/Internal/Hub.cs | 4 ++
src/Sentry/Protocol/SentryLog.cs | 13 +++-
src/Sentry/SentrySdk.cs | 14 +---
src/Sentry/SentryStructuredLogger.cs | 89 ++++++++++++++-----------
7 files changed, 99 insertions(+), 53 deletions(-)
diff --git a/src/Sentry/Extensibility/DisabledHub.cs b/src/Sentry/Extensibility/DisabledHub.cs
index a5d7541dde..a29627308c 100644
--- a/src/Sentry/Extensibility/DisabledHub.cs
+++ b/src/Sentry/Extensibility/DisabledHub.cs
@@ -1,3 +1,4 @@
+using Sentry.Infrastructure;
using Sentry.Internal;
using Sentry.Protocol.Envelopes;
using Sentry.Protocol.Metrics;
@@ -21,6 +22,7 @@ public class DisabledHub : IHub, IDisposable
private DisabledHub()
{
+ Logger = new SentryStructuredLogger(this);
}
///
@@ -228,4 +230,11 @@ public void CaptureUserFeedback(UserFeedback userFeedback)
/// No-Op.
///
public SentryId LastEventId => SentryId.Empty;
+
+ ///
+ /// Disabled Logger.
+ /// This API is experimental and it may change in the future.
+ ///
+ [Experimental(DiagnosticId.ExperimentalFeature)]
+ public SentryStructuredLogger Logger { get; }
}
diff --git a/src/Sentry/Extensibility/HubAdapter.cs b/src/Sentry/Extensibility/HubAdapter.cs
index b21eb369ee..7159ac70a4 100644
--- a/src/Sentry/Extensibility/HubAdapter.cs
+++ b/src/Sentry/Extensibility/HubAdapter.cs
@@ -32,6 +32,13 @@ private HubAdapter() { }
///
public SentryId LastEventId { [DebuggerStepThrough] get => SentrySdk.LastEventId; }
+ ///
+ /// Forwards the call to .
+ /// This API is experimental and it may change in the future.
+ ///
+ [Experimental(DiagnosticId.ExperimentalFeature)]
+ public SentryStructuredLogger Logger { [DebuggerStepThrough] get => SentrySdk.Logger; }
+
///
/// Forwards the call to .
///
diff --git a/src/Sentry/IHub.cs b/src/Sentry/IHub.cs
index abf722c89d..40896cbe7a 100644
--- a/src/Sentry/IHub.cs
+++ b/src/Sentry/IHub.cs
@@ -1,3 +1,5 @@
+using Sentry.Infrastructure;
+
namespace Sentry;
///
@@ -17,6 +19,20 @@ public interface IHub : ISentryClient, ISentryScopeManager
///
public SentryId LastEventId { get; }
+ ///
+ /// Creates and sends logs to Sentry.
+ /// This API is experimental and it may change in the future.
+ ///
+ ///
+ /// Available options:
+ ///
+ ///
+ ///
+ ///
+ ///
+ [Experimental(DiagnosticId.ExperimentalFeature)]
+ public SentryStructuredLogger Logger { get; }
+
///
/// Starts a transaction.
///
diff --git a/src/Sentry/Internal/Hub.cs b/src/Sentry/Internal/Hub.cs
index cc9cbd3ef3..0687e22fdd 100644
--- a/src/Sentry/Internal/Hub.cs
+++ b/src/Sentry/Internal/Hub.cs
@@ -57,6 +57,7 @@ internal Hub(
client ??= new SentryClient(options, randomValuesFactory: _randomValuesFactory, sessionManager: _sessionManager);
ScopeManager = scopeManager ?? new SentryScopeManager(options, client);
+ Logger = new SentryStructuredLogger(this);
if (!options.IsGlobalModeEnabled)
{
@@ -755,4 +756,7 @@ public void Dispose()
}
public SentryId LastEventId => CurrentScope.LastEventId;
+
+ [Experimental(DiagnosticId.ExperimentalFeature)]
+ public SentryStructuredLogger Logger { get; }
}
diff --git a/src/Sentry/Protocol/SentryLog.cs b/src/Sentry/Protocol/SentryLog.cs
index ce368868b3..09f0108a78 100644
--- a/src/Sentry/Protocol/SentryLog.cs
+++ b/src/Sentry/Protocol/SentryLog.cs
@@ -77,6 +77,13 @@ public required SentryLogLevel Level
[Experimental(DiagnosticId.ExperimentalFeature)]
public ImmutableArray