Skip to content

Commit c721c87

Browse files
authored
Improve MetricsEventSource error handling (#55466)
Previously exceptions that escaped user provided callbacks for observable instruments would have terminated all further metric collection. Now those exceptions are caught and reported, only potentially interfering with other observable instruments in the same collection interval.
1 parent 7d518fe commit c721c87

File tree

3 files changed

+58
-3
lines changed

3 files changed

+58
-3
lines changed

src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/AggregationManager.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ internal sealed class AggregationManager
4141
private readonly Action<Exception> _collectionError;
4242
private readonly Action _timeSeriesLimitReached;
4343
private readonly Action _histogramLimitReached;
44+
private readonly Action<Exception> _observableInstrumentCallbackError;
4445

4546
public AggregationManager(
4647
int maxTimeSeries,
@@ -54,7 +55,8 @@ public AggregationManager(
5455
Action initialInstrumentEnumerationComplete,
5556
Action<Exception> collectionError,
5657
Action timeSeriesLimitReached,
57-
Action histogramLimitReached)
58+
Action histogramLimitReached,
59+
Action<Exception> observableInstrumentCallbackError)
5860
{
5961
_maxTimeSeries = maxTimeSeries;
6062
_maxHistograms = maxHistograms;
@@ -68,6 +70,7 @@ public AggregationManager(
6870
_collectionError = collectionError;
6971
_timeSeriesLimitReached = timeSeriesLimitReached;
7072
_histogramLimitReached = histogramLimitReached;
73+
_observableInstrumentCallbackError = observableInstrumentCallbackError;
7174

7275
_listener = new MeterListener()
7376
{
@@ -351,7 +354,14 @@ private bool CheckHistogramAllowed()
351354

352355
internal void Collect()
353356
{
354-
_listener.RecordObservableInstruments();
357+
try
358+
{
359+
_listener.RecordObservableInstruments();
360+
}
361+
catch (Exception e)
362+
{
363+
_observableInstrumentCallbackError(e);
364+
}
355365

356366
foreach (KeyValuePair<Instrument, InstrumentState> kv in _instrumentStates)
357367
{

src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/MetricsEventSource.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,12 @@ public void HistogramLimitReached(string sessionId)
169169
WriteEvent(13, sessionId);
170170
}
171171

172+
[Event(14, Keywords = Keywords.TimeSeriesValues)]
173+
public void ObservableInstrumentCallbackError(string sessionId, string errorMessage)
174+
{
175+
WriteEvent(14, sessionId, errorMessage);
176+
}
177+
172178
/// <summary>
173179
/// Called when the EventSource gets a command from a EventListener or ETW.
174180
/// </summary>
@@ -303,7 +309,8 @@ public void OnEventCommand(EventCommandEventArgs command)
303309
() => Log.InitialInstrumentEnumerationComplete(sessionId),
304310
e => Log.Error(sessionId, e.ToString()),
305311
() => Log.TimeSeriesLimitReached(sessionId),
306-
() => Log.HistogramLimitReached(sessionId));
312+
() => Log.HistogramLimitReached(sessionId),
313+
e => Log.ObservableInstrumentCallbackError(sessionId, e.ToString()));
307314

308315
_aggregationManager.SetCollectionPeriod(TimeSpan.FromSeconds(refreshIntervalSecs));
309316

src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricEventSourceTests.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,33 @@ public void EventSourceEnforcesHistogramLimit()
619619
AssertCollectStartStopEventsPresent(events, IntervalSecs, 3);
620620
}
621621

622+
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))]
623+
[OuterLoop("Slow and has lots of console spew")]
624+
public void EventSourceHandlesObservableCallbackException()
625+
{
626+
using Meter meter = new Meter("TestMeter15");
627+
Counter<int> c = meter.CreateCounter<int>("counter1");
628+
ObservableCounter<int> oc = meter.CreateObservableCounter<int>("observableCounter1",
629+
(Func<int>)(() => { throw new Exception("Example user exception"); }));
630+
631+
EventWrittenEventArgs[] events;
632+
using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, IntervalSecs, "TestMeter15"))
633+
{
634+
listener.WaitForCollectionStop(s_waitForEventTimeout, 1);
635+
c.Add(5);
636+
listener.WaitForCollectionStop(s_waitForEventTimeout, 2);
637+
c.Add(12);
638+
listener.WaitForCollectionStop(s_waitForEventTimeout, 3);
639+
events = listener.Events.ToArray();
640+
}
641+
642+
AssertBeginInstrumentReportingEventsPresent(events, c, oc);
643+
AssertInitialEnumerationCompleteEventPresent(events);
644+
AssertCounterEventsPresent(events, meter.Name, c.Name, "", "", "5", "12");
645+
AssertObservableCallbackErrorPresent(events);
646+
AssertCollectStartStopEventsPresent(events, IntervalSecs, 3);
647+
}
648+
622649
private void AssertBeginInstrumentReportingEventsPresent(EventWrittenEventArgs[] events, params Instrument[] expectedInstruments)
623650
{
624651
var beginReportEvents = events.Where(e => e.EventName == "BeginInstrumentReporting").Select(e =>
@@ -833,6 +860,17 @@ private void AssertCollectStartStopEventsPresent(EventWrittenEventArgs[] events,
833860
Assert.Equal(expectedPairs, startEventsSeen);
834861
Assert.Equal(expectedPairs, stopEventsSeen);
835862
}
863+
864+
private void AssertObservableCallbackErrorPresent(EventWrittenEventArgs[] events)
865+
{
866+
var errorEvents = events.Where(e => e.EventName == "ObservableInstrumentCallbackError").Select(e =>
867+
new
868+
{
869+
ErrorText = e.Payload[1].ToString(),
870+
}).ToArray();
871+
Assert.NotEmpty(errorEvents);
872+
Assert.Contains("Example user exception", errorEvents[0].ErrorText);
873+
}
836874
}
837875

838876
class MetricsEventListener : EventListener

0 commit comments

Comments
 (0)