-
Notifications
You must be signed in to change notification settings - Fork 780
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
LogEmitter System.Diagnostics.Tracing extension project #3305
Changes from 1 commit
6d2f153
101ce19
7eb0006
20479f5
c64ca11
53a6854
fd5a87e
035823b
80cbdbb
744c399
df93cf5
1ba8afe
9a0ee32
888eb54
558e8d2
4d76b87
617d88d
ca7e319
6fae76d
a92e78e
5e33474
6b15b3b
2114dca
0d34533
bb4293c
380f139
87597f8
345e1a2
ea63af4
a12d432
3b35268
57775a5
9ee18df
f347e51
5537c73
e0c2347
dd80cc9
4f6b9e0
1dcd193
226facf
d3c5443
31f5a97
cb69e49
8693d93
b71006f
6269015
7647145
4f498af
55537b5
d1a4b3b
ffda1ce
12c92ad
cf69b8c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ OpenTelemetry.Logs.LogEmitter | |
OpenTelemetry.Logs.LogEmitter.Log(in OpenTelemetry.Logs.LogRecordData data, in OpenTelemetry.Logs.LogRecordAttributeList attributes = default(OpenTelemetry.Logs.LogRecordAttributeList)) -> void | ||
OpenTelemetry.Logs.LogEmitter.LogEmitter(OpenTelemetry.Logs.OpenTelemetryLoggerProvider! loggerProvider) -> void | ||
OpenTelemetry.Logs.LogRecord.FormattedMessage.set -> void | ||
OpenTelemetry.Logs.LogRecord.GetDataRef() -> OpenTelemetry.Logs.LogRecordData | ||
OpenTelemetry.Logs.LogRecord.State.set -> void | ||
OpenTelemetry.Logs.LogRecord.StateValues.set -> void | ||
OpenTelemetry.Logs.LogRecordAttributeList | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since .NET uses the term There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I struggled with that one. We have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤔 yea I hear ya on the struggle. I think sticking to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pretty much, just ignore I haven't tested this yet, but to help illustrate here is the Serilog sink I scratched out to mess with the public static class OpenTelemetrySerilogSinkExtensions
{
public static LoggerConfiguration OpenTelemetry(
this LoggerSinkConfiguration loggerConfiguration,
OpenTelemetryLoggerProvider openTelemetryLoggerProvider)
{
Guard.ThrowIfNull(loggerConfiguration);
return loggerConfiguration.Sink(new OpenTelemetrySerilogSink(openTelemetryLoggerProvider));
}
}
internal sealed class OpenTelemetrySerilogSink : ILogEventSink
{
private readonly LogEmitter logEmitter;
public OpenTelemetrySerilogSink(OpenTelemetryLoggerProvider openTelemetryLoggerProvider)
{
this.logEmitter = new LogEmitter(openTelemetryLoggerProvider);
}
public void Emit(LogEvent logEvent)
{
Debug.Assert(logEvent != null, "LogEvent was null.");
LogRecordData data = new(Activity.Current)
{
Timestamp = logEvent!.Timestamp.UtcDateTime,
LogLevel = (LogLevel)(int)logEvent.Level,
Message = logEvent.MessageTemplate.Text,
Exception = logEvent.Exception,
};
LogRecordAttributeList attributes = default;
foreach (KeyValuePair<string, LogEventPropertyValue> property in logEvent.Properties)
{
// TODO: Serilog supports complex type logging. This is not yet
// supported in OpenTelemetry.
if (property.Key == Constants.SourceContextPropertyName)
{
data.CategoryName = property.Value.ToString();
}
else if (property.Value is ScalarValue scalarValue)
{
attributes.Add(property.Key, scalarValue.Value);
}
else if (property.Value is SequenceValue sequenceValue)
{
IReadOnlyList<LogEventPropertyValue> elements = sequenceValue.Elements;
if (elements.Count > 0 && elements[0] is ScalarValue)
{
object[] values = new object[elements.Count];
for (int i = 0; i < elements.Count; i++)
{
if (elements[i] is ScalarValue value)
{
values[i] = value.Value;
}
}
attributes.Add(property.Key, values);
}
}
}
this.logEmitter.Log(in data, in attributes);
}
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cool, yea this example is great. Is the reason due to size/performance that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Originally I had the attributes as a field on logEmitter.Log(new()
{
Message = "Hello world",
Attributes = { ["count" ] = 1, ["id"] = 18 }
}) That would work if "Attributes" was a reference type, but compiler doesn't allow with a value type! I tried a bunch of things, ultimately moved it to a separate parameter. At the moment you can do... logEmitter.Log(
new() { Message = "Hello world" },
new() { ["count" ] = 1, ["id"] = 18 }
}) Besides that...
At one point I tried a builder pattern like... logEmitter.Log(new()
.SetMessage("Hello world")
.AddAttribute("count", 1)) But that is tricky to do with a struct. Each time you return the struct it is a new copy so you have to write it like |
||
|
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 confirming my own understanding of the improvements this PR brings... so this is more performant because accessing properties of the struct results in a
call
vs.callvirt
, right?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 particular change is more to prevent a perf regression! This method used to work something like...
(Because
LogRecord
is sealed it should already be acall
instead ofcallvirt
.)Now due to the pooling changes it has to do...
Extra call!
Using
GetDataRef()
it goes back to a single call like...Just an optimization to cut out the extra hops.
True perf really comes down to how the JIT decides to inline things. Here's a sharplab if you want to mess with 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.
So what are the real perf gains on this PR?
I will attempt to answer this 😄
Our log logic was like this:
And is now like this:
Should be slower, right?
Turns out, is only slightly slower (when hitting the
[ThreadStatic]
):But we eliminated all the memory pressure. I think this is why the stress test is performing better with the pool. Only a few more CPU cycles to use the pool but the GC is asleep freeing up the CPU to process more logs. This is a largely a guess but kind of makes sense?
Now that benchmark is interesting. How is it introducing the pool logic made it only slightly slower than just calling ctor?
Check out this benchmark:
Number of fields on the class impacts the ctor time.
LogRecord
has a lot of fields so that gives us some wiggle room to execute our pool logic before just calling the ctor would be faster. Interesting, eh?