-
-
Notifications
You must be signed in to change notification settings - Fork 219
WIP: Sentry Logs #4158
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feat/logs
Are you sure you want to change the base?
WIP: Sentry Logs #4158
Conversation
Instructions and example for changelogPlease add an entry to Example: ## Unreleased
- Sentry Logs ([#4158](https://github.com/getsentry/sentry-dotnet/pull/4158)) If none of the above apply, you can opt out of this check by adding |
namespace Sentry.Experimental; | ||
|
||
//TODO: QUESTION: not sure about the name | ||
// this is a bit different to Sentry.SentryLevel and Sentry.BreadcrumbLevel |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it need to be different? I wonder if we can use SentryLevel instead?
Because we don't have Trace
there? :~
We could add it as -1
😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, not necessarily.
And adding it as -1
would not break any existing behavior - but may warrant some documentation - like:
public static LogLevel ToMicrosoft(this SentryLevel level) |
But reusing Sentry.SentryLevel
could be confusing:
public enum SentryLevel : short |
uses the
EnumMemberAttribute
, with e.g. [EnumMember(Value = "warning")] Warning,
. But the OTEL SeverityText
field and our Protocol expects "warn"
instead. Well, we would still have the separate ToSentryProtocolLevel
(or similar) extension method that does the right thing, but the re-use may cause confusion.
Also, this would have a side effect onto existing usages, viaSentryOptions.DiagnosticLevel
:
public abstract class DiagnosticLogger : IDiagnosticLogger |
Do we want to extend the "Log-Level" for existing internal/diagnostic logging?
On the other hand, adding yet another "Log-Level" enum, that is similar, but not quite the same, is similarly confusing.
And now I am thinking if we could get away with making this new enum type internal
instead, since we do have separate Log{Level}()
methods, and application code could still change the severity via the int
-based SeverityNumber
through the BeforeSendLog
-callback. (see Log Severity Number)
To, for now, reduce the public surface area, and we could think about which approach to chose when/if the issue arises later.
AttributeTargets.Event | | ||
AttributeTargets.Interface | | ||
AttributeTargets.Delegate, Inherited = false)] | ||
internal sealed class ExperimentalAttribute : Attribute |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@SimonCropp has a shimming library that might have this, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is indeed!
I only checked the README
where I didn't find it, but since v1.32.0
it is included (I'll PR Polyfill
's docs later).
Now I got two questions:
- Should we update to
v1.32.0
, or the latest version ofPolyfill
(v7.27.0
). - Depending on the version we chose: should we update this in a separate PR?
- e.g.
v1.34.0
adds theSystem.Runtime.CompilerServices.DefaultInterpolatedStringHandler
which the compiler then chooses for interpolated strings- this should have a positive effect on performance when using interpolated strings in our codebase targeting TFMs less than
net6.0
- this should have a positive effect on performance when using interpolated strings in our codebase targeting TFMs less than
- e.g.
{ | ||
//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<SentryLog>? configureLog = null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, could we have overloads with T
instead to avoid boxing cost when logging is disabled?
That's the approach taken on the diagnostic logger extensions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it's also on my bucket list ... I updated the description to reflect required work, too.
I'd not only like to add up to 3 TArg
overloads per severity, but also - in a follow-up PR - add a custom [InterpolatedStringHandler]
, which I'm going to explore over the weekend.
// 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<SentryLog>? configureLog) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we going to do batching here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CaptureLogs
as well?
The spec allows for multiple envelope items... which would be more efficient in apps that are emitting loads of logs. We'd need to build some fancy logic to queue/flush logs in that case. Probably worth it from a performance point of view though since logs are the kind of thing people might send oodles of.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, batching is amongst my TODOs ... I updated the description with open work during the draft status.
Exciting first step in our logging story :) |
@Flash0ver can we reference some developer docs and/or an issue in the PR description to help reviewers get an overview of the change before looking at code? |
@@ -8,4 +8,13 @@ internal static class DiagnosticId | |||
/// </summary> | |||
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"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's OK to just have one... the main purpose here is to make sure people are aware/deliberate about opting into our experimental APIs. They'll get a separate warning in their code each time they use one/any of them.
Potentially they could be disabling those in a directory.build.props file for their solution... in which case separate diagnostic ids would mean they'd need to disable warnings for our Heap Dumps feature and the Logs feature separately... seems like overkill.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SGTM
I am now thinking about adding a single UrlFormat
string, used by both/all experimental features, linking to a dedicated yet small "Experimental Features" page in our docs. But will follow up with that in another PR.
@@ -472,6 +472,17 @@ public static void WriteSerializable( | |||
writer.WriteSerializableValue(value, logger); | |||
} | |||
|
|||
public static void WriteSerializable<TValue>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How is this different to the overload defined on line 465? The only difference I can see is the struct constraint in the where clause (but is this important or used for anything)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The overload above
sentry-dotnet/src/Sentry/Internal/Extensions/JsonExtensions.cs
Lines 465 to 469 in 8978bc4
public static void WriteSerializable( | |
this Utf8JsonWriter writer, | |
string propertyName, | |
ISentryJsonSerializable value, | |
IDiagnosticLogger? logger) |
takes an
ISentryJsonSerializable
, and is boxing struct-based ISentryJsonSerializable
to the interface type.
My original motivation was to add a new overload that avoids boxing for struct-based ISentryJsonSerializable
to not binary-break existing call sites, but just realize that we have 11 existing call sites binding to the new overload.
IIRC we're not too concerned about binary compatibility, but definitely about source compat, since user's usually only consume one Sentry-package, or at least keep the Sentry-Package-Versions in sync, so I'm thinking about changing the existing WriteSerializable
to be generic.
Also, I just realize that the only usages of the method is within the Sentry
assembly, so there are not even binary breaking concerns at all.
TL;DR
I'd like to change the existing WriteSerializable
to be generic in order to avoid boxing allocations for struct-based ISentryJsonSerializable
.
But now I believe I changed my mind and I'd like to propose this change in a separate PR, to not mix too many concerns in a single changeset.
CaptureLog(SentrySeverity.Trace, template, parameters, configureLog); | ||
} | ||
|
||
//TODO: QUESTION: parameter name "template" vs "format" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like it's less mental gymnastics for us if we go with the sentry naming conventions (when we're reading code from other eepos etc. to see how they make changes that we need to mimic). So I'd probably go with template
personally.
@@ -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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, we've got two types called SentryLogger
now? Sentry.SentryLogger
and also Sentry.Extensions.Logging.SentryLogger
? I can see that being a pain over time. I reckon we rename one of them.
Sentry.Extensions.Logging.SentryLogger
is internal sealed
so no issues renaming that if we have to. SentryMelLogger
maybe?
More painful would be things like SentryLoggingOptions, since that's public. If we need some dedicated options for these new features, we could call them SentryStructuredLoggingOptions
maybe... In which case possibly we call the new logger the SentryStructuredLogger
(for consistency)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are all of these public? Sentry.Extensions.Logging.SentryLogger
internal sealed class SentryLogger : ILogger |
This is internal so less of a concern
Looking good so far 👍🏻 |
Draft PR: Work in progress
Info
TODO
log
Envelope Item PayloadFollow-ups
object[]
but generic<TArg>
overloads per Log-Severity method for the most common scenariosoptions.EnableLogs is false
IDiagnosticLogger.Log{Level}()
extension methods have overloads with up to 3 generic type parametersString.Format
has 1-3 object argumentsnet8.0
when used withCompositeFormat
[InterpolatedStringHandler]
to support interpolated strings and ensure zero-alloc ifoptions.EnableLogs is false
net6.0
Early feedback appreciated on
see also Question-Comments in the draft