-
-
Notifications
You must be signed in to change notification settings - Fork 220
feat(logs): initial API for 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?
Conversation
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.
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.
We currently still have a new enum
.
Renamed to SentryLogLevel
. Matches the sentry-java
SDK.
Also moved to Sentry
namespace.
src/Sentry/Experimental/System.Diagnostics.CodeAnalysis.ExperimentalAttribute.cs
Outdated
Show resolved
Hide resolved
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? |
test/Sentry.DiagnosticSource.IntegrationTests/SqlListenerTests.verify.cs
Outdated
Show resolved
Hide resolved
Looking good so far 👍🏻 |
…sentry-dotnet into feat/logs-initial-api
src/Sentry/SentryStructuredLogger.cs
Outdated
|
||
private void CaptureLog(SentryLogLevel level, string template, object[]? parameters, Action<SentryLog>? configureLog) | ||
{ | ||
Debug.Assert(_options is not 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.
why not IsEnabled check here? and by pass all the public method checks
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 wanted to avoid the CaptureLog
method invocation from the public Log{Level}
methods if logging is not enabled.
This assert is to let null-flow analysis know that the options are not null here, because we already checked in the calling method.
Changed to Debug.Assert(IsEnabled);
to be more explicit about the expected flow.
…delegate exceptions
Co-authored-by: Bruno Garcia <bruno@brunogarcia.com>
Co-Authored-By: Bruno Garcia <bruno-garcia@users.noreply.github.com>
|
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.
Looking great.
The main suggestion I've left is about testability... which I think you'd bump into if you tried to create some unit tests for the new methods that have been added to theHub
class.
@@ -33,6 +33,19 @@ | |||
|
|||
// This option tells Sentry to capture 100% of traces. You still need to start transactions and spans. | |||
options.TracesSampleRate = 1.0; | |||
|
|||
// This option enables the (experimental) Sentry Logs. | |||
options.EnableLogs = true; |
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.
If/while these are experimental, should we put them in an options.Experimental.EnableLogs
section of the options (to make that unavoidably apparent to SDK users)?
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 guess because they have [Experimental(Infrastructure.DiagnosticId.ExperimentalFeature)]
it could be OK without it?
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's almost worth writing a source generator to maintain this file... so much boilerplate. Not for this PR obviously.
[Experimental(DiagnosticId.ExperimentalFeature)] | ||
public void LogTrace(string template, object[]? parameters = null, Action<SentryLog>? configureLog = null) | ||
{ | ||
if (IsEnabled) |
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 looks like all of the public methods have this IsEnabled
check and then delegate all the work to the CaptureLog
method.
We could centralise this logic by putting the check for IsEnabled
at the top of the CaptureLog
method instead.
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.
Alternatively, if we did add an ISentryStructuredLogger
interface, we could have two implementations of that interface: SentryStructuredLogger
and DisabledSentryStructuredLogger
... the implementation for the later would just be a bunch of no-ops.
One advantage of that approach is that we could mark the dependencies for this implementation as non nullable. I'm not sure if that would simplify things enough to be worth while though (it's a pretty simple class in the first place).
Your call I think.
/// </list> | ||
/// </remarks> | ||
[Experimental(Infrastructure.DiagnosticId.ExperimentalFeature)] | ||
public SentryStructuredLogger Logger { get; } |
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.
Just thinking about testing of the Hub classes in general, the Logger
is kind of a dependency of the hub and currently it's not very easy to inject/mock.
Maybe worth creating an ISentryStructuredLogger
interface and having this member be of type ISentryStructuredLogger
rather than SentryStructuredLogger
. A mock Logger could then optionally be injected via the constructor for test purposes.
namespace Sentry; | ||
|
||
/// <summary> | ||
/// Represents the Sentry Log protocol. |
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.
Is there any reason this isn't in the Sentry.Protocol
namespace?
/// <remarks> | ||
/// Sent as seconds since the Unix epoch. | ||
/// </remarks> | ||
[Experimental(DiagnosticId.ExperimentalFeature)] |
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.
Possibly overkill decorating every property with this... the class is already decorated with Experimental
.
I think it's enough if we put the option to enable this feature under SentryOptions.Experimenta
and then decorate any public methods in the API (the stuff on IHub
I'm guessing).
Potentially we could decorate public classes like this as well, although I can't see anybody using these classes independently of the experimental APIs they support.
/// The severity of the structured log. | ||
/// <para>This API is experimental and it may change in the future.</para> | ||
/// </summary> | ||
/// <seealso href="https://develop.sentry.dev/sdk/telemetry/logs/"/> |
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.
Maybe worth adding something to the comments here about the significance of the int
values assigned to each of the enum members. I was initially a bit surprised we were assigning values here... only worked out why by reading these docs:
|
||
reader.EndOfStream.Should().BeTrue(); | ||
|
||
header.ToIndentedJsonString().Should().Be($$""" |
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.
This is the sort of thing Verify is good at testing... might be a good excuse to get familiar with that.
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.
just went half the PR this time but leaving some notes for now
@@ -33,6 +33,19 @@ | |||
|
|||
// This option tells Sentry to capture 100% of traces. You still need to start transactions and spans. | |||
options.TracesSampleRate = 1.0; | |||
|
|||
// This option enables the (experimental) Sentry Logs. | |||
options.EnableLogs = true; |
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 guess because they have [Experimental(Infrastructure.DiagnosticId.ExperimentalFeature)]
it could be OK without it?
options.EnableLogs = true; | ||
options.SetBeforeSendLog(static log => | ||
{ | ||
if (log.TryGetAttribute("suppress", out bool attribute) && 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.
if (log.TryGetAttribute("suppress", out bool attribute) && attribute) | |
// A demonstration of how you can drop logs based on some attribute they have | |
if (log.TryGetAttribute("suppress", out bool attribute) && attribute) |
@@ -88,6 +104,8 @@ async Task ThirdFunction() | |||
// Simulate doing some work | |||
await Task.Delay(100); | |||
|
|||
SentrySdk.Logger.LogFatal("Crash imminent!", [], static log => log.SetAttribute("suppress", true)); |
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.
Odd that we must pass an empty array as a second argument.
There's no way we can set up the overloads in a way that this is not needed?
{ | ||
internal static void WriteAttribute(Utf8JsonWriter writer, string propertyName, SentryAttribute attribute) | ||
{ | ||
Debug.Assert(attribute.Type is not 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.
do we need this assert on all calls?
it's odd to have it here but no on the others
Or only on the method these all fall into: WriteAttributeValue
?
Draft PR: Work in progress
Info
TODO
Follow-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
log
Envelope Item PayloadEarly feedback appreciated on
see also Question-Comments in the draft