-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
LoggingService.cs
1933 lines (1687 loc) · 87.7 KB
/
LoggingService.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Threading;
using Microsoft.Build.BackEnd.Components.RequestBuilder;
using Microsoft.Build.Experimental.BuildCheck.Infrastructure;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using InternalLoggerException = Microsoft.Build.Exceptions.InternalLoggerException;
using LoggerDescription = Microsoft.Build.Logging.LoggerDescription;
#nullable disable
namespace Microsoft.Build.BackEnd.Logging
{
/// <summary>
/// What is the mode of the logger, should there be a thread
/// processing the buildEvents and raising them on the filters and sinks
/// or should they be done synchronously
/// </summary>
internal enum LoggerMode
{
/// <summary>
/// Events are processed synchronously
/// </summary>
Synchronous,
/// <summary>
/// A thread is started which will process build events by raising them on a filter event source
/// or on the correct sink.
/// </summary>
Asynchronous
}
/// <summary>
/// What is the current state of the logging service
/// </summary>
internal enum LoggingServiceState
{
/// <summary>
/// When the logging service has been instantiated but not yet initialized through a call
/// to initializecomponent
/// </summary>
Instantiated,
/// <summary>
/// The logging service has been initialized through a call to initialize component
/// </summary>
Initialized,
/// <summary>
/// The logging service is in the process of starting to shutdown.
/// </summary>
ShuttingDown,
/// <summary>
/// The logging service completly shutdown
/// </summary>
Shutdown
}
/// <summary>
/// Logging services is used as a helper class to assist logging messages in getting to the correct loggers.
/// </summary>
internal partial class LoggingService : ILoggingService, INodePacketHandler, IBuildComponent
{
/// <summary>
/// The default maximum size for the logging event queue.
/// </summary>
private const uint DefaultQueueCapacity = 200000;
/// <summary>
/// Lock for the nextProjectId
/// </summary>
private readonly object _lockObject = new Object();
/// <summary>
/// A cached reflection accessor for an internal member.
/// </summary>
/// <remarks>
/// We use a BindingFlags.Public flag here because the getter is public, so although the setter is internal,
/// it is only discoverable with Reflection using the Public flag (go figure!)
/// </remarks>
private static Lazy<PropertyInfo> s_projectStartedEventArgsGlobalProperties = new Lazy<PropertyInfo>(() => typeof(ProjectStartedEventArgs).GetProperty("GlobalProperties", BindingFlags.Public | BindingFlags.Instance), LazyThreadSafetyMode.PublicationOnly);
/// <summary>
/// A cached reflection accessor for an internal member.
/// </summary>
/// <remarks>
/// We use a BindingFlags.Public flag here because the getter is public, so although the setter is internal,
/// it is only discoverable with Reflection using the Public flag (go figure!)
/// </remarks>
private static Lazy<PropertyInfo> s_projectStartedEventArgsToolsVersion = new Lazy<PropertyInfo>(() => typeof(ProjectStartedEventArgs).GetProperty("ToolsVersion", BindingFlags.Public | BindingFlags.Instance), LazyThreadSafetyMode.PublicationOnly);
#region Data
/// <summary>
/// The mapping of build request configuration ids to project file names.
/// </summary>
private ConcurrentDictionary<int, string> _projectFileMap;
/// <summary>
/// The current state of the logging service
/// </summary>
private LoggingServiceState _serviceState;
/// <summary>
/// Use to optimize away status messages. When this is set to true, only "critical"
/// events like errors are logged. Default is false
/// </summary>
private bool _onlyLogCriticalEvents;
/// <summary>
/// Contains a dictionary of loggerId's and the sink which the logger (of the given Id) is expecting to consume its messages
/// </summary>
private Dictionary<int, IBuildEventSink> _eventSinkDictionary;
/// <summary>
/// A list of ILoggers registered with the LoggingService
/// </summary>
private List<ILogger> _loggers;
/// <summary>
/// A list of LoggerDescriptions which describe how to create a forwarding logger on a node. These are
/// passed to each node as they are created so that the forwarding loggers can be registered on them.
/// </summary>
private List<LoggerDescription> _loggerDescriptions;
/// <summary>
/// The event source to which filters will listen to get the build events which are logged to the logging service through the
/// logging helper methods. Ie LogMessage and LogMessageEvent
/// </summary>
private EventSourceSink _filterEventSource;
/// <summary>
/// Index into the eventSinkDictionary which indicates which sink is the sink for any logger registered through RegisterLogger
/// </summary>
private int _centralForwardingLoggerSinkId = -1;
/// <summary>
/// What is the Id for the next logger registered with the logging service.
/// This Id is unique for this instance of the loggingService.
/// </summary>
private int _nextSinkId = 0;
/// <summary>
/// The number of nodes in the system. Loggers may take different action depending on how many nodes are in the system.
/// </summary>
private int _maxCPUCount = 1;
/// <summary>
/// Component host for this component which is used to get system parameters and other initialization information.
/// </summary>
private IBuildComponentHost _componentHost;
/// <summary>
/// The IConfigCache instance obtained from componentHost (stored here to avoid repeated dictionary lookups).
/// </summary>
private Lazy<IConfigCache> _configCache;
/// <summary>
/// The next project ID to assign when a project evaluation started event is received.
/// </summary>
private int _nextEvaluationId;
/// <summary>
/// The next project ID to assign when a project started event is received.
/// </summary>
private int _nextProjectId;
/// <summary>
/// The next target ID to assign when a target started event is received.
/// </summary>
private int _nextTargetId = 1;
/// <summary>
/// The next task ID to assign when a task started event is received.
/// </summary>
private int _nextTaskId = 1;
/// <summary>
/// What node is this logging service running on
/// </summary>
private int _nodeId = 0;
/// <summary>
/// Whether to include evaluation metaprojects in events.
/// </summary>
private bool? _includeEvaluationMetaprojects;
/// <summary>
/// Whether to include evaluation profiles in events.
/// </summary>
private bool? _includeEvaluationProfile;
/// <summary>
/// Whether properties and items should be logged on <see cref="ProjectEvaluationFinishedEventArgs"/>
/// instead of <see cref="ProjectStartedEventArgs"/>.
/// </summary>
private bool? _includeEvaluationPropertiesAndItems;
/// <summary>
/// Whether to include task inputs in task events.
/// </summary>
private bool? _includeTaskInputs;
/// <summary>
/// A list of build submission IDs that have logged errors. If an error is logged outside of a submission, the submission ID is <see cref="BuildEventContext.InvalidSubmissionId"/>.
/// </summary>
private readonly ISet<int> _buildSubmissionIdsThatHaveLoggedErrors = new HashSet<int>();
/// <summary>
/// A list of warnings to treat as errors for an associated <see cref="BuildEventContext"/>. If an empty set, all warnings are treated as errors.
/// </summary>
private IDictionary<WarningsConfigKey, ISet<string>> _warningsAsErrorsByProject;
/// <summary>
/// A list of warnings to not to be promoted to errors for an associated <see cref="BuildEventContext"/>.
/// </summary>
private IDictionary<WarningsConfigKey, ISet<string>> _warningsNotAsErrorsByProject;
/// <summary>
/// A list of warnings to treat as messages for an associated <see cref="BuildEventContext"/>.
/// </summary>
private IDictionary<WarningsConfigKey, ISet<string>> _warningsAsMessagesByProject;
/// <summary>
/// The minimum message importance that must be logged because there is a possibility that a logger consumes it.
/// Null means that the optimization is disabled or no relevant logger has been registered.
/// </summary>
private MessageImportance? _minimumRequiredMessageImportance;
#region LoggingThread Data
/// <summary>
/// Queue for asynchronous event processing.
/// </summary>
private ConcurrentQueue<object> _eventQueue;
/// <summary>
/// Event set when message is consumed from queue.
/// </summary>
private AutoResetEvent _dequeueEvent;
/// <summary>
/// Event set when queue become empty.
/// </summary>
private ManualResetEvent _emptyQueueEvent;
/// <summary>
/// Even set when message is added into queue.
/// </summary>
private AutoResetEvent _enqueueEvent;
/// <summary>
/// CTS for stopping logging event processing.
/// </summary>
private CancellationTokenSource _loggingEventProcessingCancellation;
/// <summary>
/// Task which pump/process messages from <see cref="_eventQueue"/>
/// </summary>
private Thread _loggingEventProcessingThread;
/// <summary>
/// The queue size above which the queue will close to messages from remote nodes.
/// This value should be selected such that during normal builds it is never reached.
/// It should also be low enough that we do not accumulate enough messages to cause
/// virtual memory exhaustion in extremely large builds.
/// </summary>
private uint _queueCapacity = DefaultQueueCapacity;
/// <summary>
/// By default our logMode is Asynchronous. We do this
/// because we are hoping it will make the system
/// more responsive when there are a large number of logging messages
/// </summary>
private LoggerMode _logMode = LoggerMode.Asynchronous;
#endregion
#endregion
#region Constructors
/// <summary>
/// Initialize an instance of a loggingService.
/// </summary>
/// <param name="loggerMode">Should the events be processed synchronously or asynchronously</param>
/// <param name="nodeId">The node identifier.</param>
protected LoggingService(LoggerMode loggerMode, int nodeId)
{
_projectFileMap = new ConcurrentDictionary<int, string>();
_logMode = loggerMode;
_loggers = new List<ILogger>();
_loggerDescriptions = new List<LoggerDescription>();
_eventSinkDictionary = new Dictionary<int, IBuildEventSink>();
_nodeId = nodeId;
_configCache = new Lazy<IConfigCache>(() => (IConfigCache)_componentHost.GetComponent(BuildComponentType.ConfigCache), LazyThreadSafetyMode.PublicationOnly);
// Start the project context id count at the nodeId
_nextProjectId = nodeId;
_nextEvaluationId = nodeId;
string queueCapacityEnvironment = Environment.GetEnvironmentVariable("MSBUILDLOGGINGQUEUECAPACITY");
if (!String.IsNullOrEmpty(queueCapacityEnvironment))
{
if (UInt32.TryParse(queueCapacityEnvironment, out uint localQueueCapacity))
{
_queueCapacity = localQueueCapacity;
}
_queueCapacity = Math.Max(0, _queueCapacity);
}
if (_logMode == LoggerMode.Asynchronous)
{
StartLoggingEventProcessing();
}
// Ensure the static constructor of ItemGroupLoggingHelper runs.
// It is important to ensure the Message delegate on TaskParameterEventArgs is set.
_ = ItemGroupLoggingHelper.ItemGroupIncludeLogMessagePrefix;
_serviceState = LoggingServiceState.Instantiated;
}
#endregion
#region Events
/// <summary>
/// When there is an exception on the logging thread, we do not want to throw the exception from there
/// instead we would like the exception to be thrown on the engine thread as this is where hosts expect
/// to see the exception. This event will transport the exception from the loggingService to the engine
/// which will register on this event.
/// </summary>
public event LoggingExceptionDelegate OnLoggingThreadException;
/// <summary>
/// Raised when a ProjectStarted event is about to be sent to the loggers.
/// </summary>
public event ProjectStartedEventHandler OnProjectStarted;
/// <summary>
/// Raised when a ProjectFinished event has just been sent to the loggers.
/// </summary>
public event ProjectFinishedEventHandler OnProjectFinished;
#endregion
#region Properties
/// <summary>
/// Properties we need to serialize from the child node
/// </summary>
public string[] PropertiesToSerialize
{
get;
set;
}
/// <summary>
/// Should all properties be serialized from the child to the parent node
/// </summary>
public bool SerializeAllProperties
{
get;
set;
}
/// <summary>
/// Is the logging running on a remote node
/// </summary>
public bool RunningOnRemoteNode
{
get;
set;
}
/// <summary>
/// Gets the next project evaluation id.
/// </summary>
/// <remarks>This property is thread-safe</remarks>
public int NextEvaluationId
{
get
{
// We can create one node more than the maxCPU count (this can happen if either the inproc or out of proc node has not been created yet and the project collection needs to be counted also)
return Interlocked.Add(ref _nextEvaluationId, MaxCPUCount + 2);
}
}
/// <summary>
/// Gets the next project id.
/// </summary>
/// <remarks>This property is thread-safe</remarks>
public int NextProjectId
{
get
{
// We can create one node more than the maxCPU count (this can happen if either the inproc or out of proc node has not been created yet and the project collection needs to be counted also)
return Interlocked.Add(ref _nextProjectId, MaxCPUCount + 2);
}
}
/// <summary>
/// Gets the next target id.
/// </summary>
/// <remarks>This property is thread-safe</remarks>
public int NextTargetId
{
get
{
return Interlocked.Increment(ref _nextTargetId);
}
}
/// <summary>
/// Gets the next task id.
/// </summary>
/// <remarks>This property is thread-safe</remarks>
public int NextTaskId
{
get
{
return Interlocked.Increment(ref _nextTaskId);
}
}
/// <summary>
/// Provide the current state of the loggingService.
/// Is it Inistantiated
/// Has it been Initialized
/// Is it starting to shutdown
/// Has it shutdown
/// </summary>
public LoggingServiceState ServiceState => _serviceState;
/// <summary>
/// Use to optimize away status messages. When this is set to true, only "critical"
/// events like errors are logged.
/// </summary>
public bool OnlyLogCriticalEvents
{
get => _onlyLogCriticalEvents;
set => _onlyLogCriticalEvents = value;
}
/// <summary>
/// Number of nodes in the system when the system is initially started
/// </summary>
public int MaxCPUCount
{
get => _maxCPUCount;
set => _maxCPUCount = value;
}
/// <summary>
/// Gets or sets a value that will error when the build process fails an up-to-date check.
/// </summary>
public bool Question { get; set; }
/// <summary>
/// The list of descriptions which describe how to create forwarding loggers on a node.
/// This is used by the node provider to get a list of registered descriptions so that
/// they can be transmitted to child nodes.
/// </summary>
public ICollection<LoggerDescription> LoggerDescriptions => _loggerDescriptions;
/// <summary>
/// Enumerator over all registered loggers.
/// </summary>
public ICollection<ILogger> Loggers => _loggers;
/// <summary>
/// What type of logging mode is the logger running under.
/// Is it Synchronous or Asynchronous
/// </summary>
public LoggerMode LoggingMode => _logMode;
/// <summary>
/// Get of warnings to treat as errors. An empty non-null set will treat all warnings as errors.
/// </summary>
public ISet<string> WarningsAsErrors
{
get;
set;
} = null;
/// <summary>
/// Get of warnings to not treat as errors. Only has any effect if WarningsAsErrors is empty but not null.
/// </summary>
public ISet<string> WarningsNotAsErrors
{
get;
set;
} = null;
/// <summary>
/// A list of warnings to treat as low importance messages.
/// </summary>
public ISet<string> WarningsAsMessages
{
get;
set;
} = null;
/// <summary>
/// Should evaluation events include generated metaprojects?
/// </summary>
public bool IncludeEvaluationMetaprojects
{
get => _includeEvaluationMetaprojects ??= _eventSinkDictionary.Values.OfType<EventSourceSink>().Any(sink => sink.IncludeEvaluationMetaprojects);
set => _includeEvaluationMetaprojects = value;
}
/// <summary>
/// Should evaluation events include profiling information?
/// </summary>
public bool IncludeEvaluationProfile
{
get => _includeEvaluationProfile ??= _eventSinkDictionary.Values.OfType<EventSourceSink>().Any(sink => sink.IncludeEvaluationProfiles);
set => _includeEvaluationProfile = value;
}
/// <summary>
/// Should task events include task inputs?
/// </summary>
public bool IncludeTaskInputs
{
get => _includeTaskInputs ??= _eventSinkDictionary.Values.OfType<EventSourceSink>().Any(sink => sink.IncludeTaskInputs);
set => _includeTaskInputs = value;
}
/// <summary>
/// Should properties and items be logged on <see cref="ProjectEvaluationFinishedEventArgs"/>
/// instead of <see cref="ProjectStartedEventArgs"/>?
/// </summary>
public bool IncludeEvaluationPropertiesAndItems
{
get
{
if (_includeEvaluationPropertiesAndItems == null)
{
var escapeHatch = Traits.Instance.EscapeHatches.LogPropertiesAndItemsAfterEvaluation;
if (escapeHatch.HasValue)
{
_includeEvaluationPropertiesAndItems = escapeHatch.Value;
}
else
{
var sinks = _eventSinkDictionary.Values.OfType<EventSourceSink>();
// .All() on an empty list defaults to true, we want to default to false
_includeEvaluationPropertiesAndItems = sinks.Any() && sinks.All(sink => sink.IncludeEvaluationPropertiesAndItems);
}
}
return _includeEvaluationPropertiesAndItems ?? false;
}
set => _includeEvaluationPropertiesAndItems = value;
}
/// <summary>
/// Determines if the specified submission has logged an errors.
/// </summary>
/// <param name="submissionId">The ID of the build submission. A value of "0" means that an error was logged outside of any build submission.</param>
/// <returns><code>true</code> if the build submission logged an errors, otherwise <code>false</code>.</returns>
public bool HasBuildSubmissionLoggedErrors(int submissionId)
{
// Warnings as errors are not tracked if the user did not specify to do so
if (WarningsAsErrors == null && _warningsAsErrorsByProject == null)
{
return false;
}
// Determine if any of the event sinks have logged an error with this submission ID
return _buildSubmissionIdsThatHaveLoggedErrors?.Contains(submissionId) == true;
}
/// <summary>
/// Returns a collection of warnings to be logged as errors for the specified build context.
/// </summary>
/// <param name="context">The build context through which warnings will be logged as errors.</param>
/// <returns>
/// </returns>
public ICollection<string> GetWarningsAsErrors(BuildEventContext context)
{
return GetWarningsForProject(context, _warningsAsErrorsByProject, WarningsAsErrors);
}
/// <summary>
/// Returns a collection of warnings not to be logged as errors for the specified build context.
/// </summary>
/// <param name="context">The build context through which warnings will be kept as warnings.</param>
/// <returns>
/// </returns>
public ICollection<string> GetWarningsNotAsErrors(BuildEventContext context)
{
return GetWarningsForProject(context, _warningsNotAsErrorsByProject, WarningsNotAsErrors);
}
/// <summary>
/// Returns a collection of warnings to be demoted to messages for the specified build context.
/// </summary>
/// <param name="context">The build context through which warnings will be logged as messages.</param>
/// <returns>
/// </returns>
public ICollection<string> GetWarningsAsMessages(BuildEventContext context)
{
return GetWarningsForProject(context, _warningsAsMessagesByProject, WarningsAsMessages);
}
/// <summary>
/// Helper method that unifies the logic for GetWarningsAsErrors, GetWarningsNotAsErrors, and GetWarningsAsMessages.
/// Specifically, this method returns a collection of codes that, within the context of a particular project, should
/// be treated specially. These tend to come from setting the associated properties in the project file. These are
/// added to previously known codes as necessary.
/// </summary>
/// <param name="context">The specific context in which to consider special treatment for warnings.</param>
/// <param name="warningsByProject">A dictionary of all warnings to be treated special by for which projects.</param>
/// <param name="warnings">Warning codes we already know should be promoted, demoted, or not promoted as relevant.</param>
/// <returns></returns>
private ICollection<string> GetWarningsForProject(BuildEventContext context, IDictionary<WarningsConfigKey, ISet<string>> warningsByProject, ISet<string> warnings)
{
WarningsConfigKey key = GetWarningsConfigKey(context);
if (warningsByProject != null && warningsByProject.TryGetValue(key, out ISet<string> newWarnings))
{
if (warnings != null)
{
newWarnings.UnionWith(warnings);
}
return newWarnings;
}
else
{
return warnings;
}
}
/// <summary>
/// Adds warning codes that should be treated as errors to the known set.
/// </summary>
/// <param name="buildEventContext">The context in which to consider possible warnings to be promoted.</param>
/// <param name="codes">Codes to promote</param>
public void AddWarningsAsErrors(BuildEventContext buildEventContext, ISet<string> codes)
{
AddWarningsAsMessagesOrErrors(ref _warningsAsErrorsByProject, buildEventContext, codes);
}
/// <summary>
/// Adds warning codes that should not be treated as errors even if WarnAsError is empty (specifying that all warnings should be promoted).
/// </summary>
/// <param name="buildEventContext">The context in which to consider warnings not to be promoted.</param>
/// <param name="codes">Codes not to promote</param>
public void AddWarningsNotAsErrors(BuildEventContext buildEventContext, ISet<string> codes)
{
AddWarningsAsMessagesOrErrors(ref _warningsNotAsErrorsByProject, buildEventContext, codes);
}
/// <summary>
/// Adds warning codes that should be treated as messages.
/// </summary>
/// <param name="buildEventContext">The context in which to consider warnings to be demoted.</param>
/// <param name="codes">Codes to demote</param>
public void AddWarningsAsMessages(BuildEventContext buildEventContext, ISet<string> codes)
{
AddWarningsAsMessagesOrErrors(ref _warningsAsMessagesByProject, buildEventContext, codes);
}
/// <summary>
/// Adds warning codes to be treated or not treated as warnings or errors to the set of project-specific codes.
/// </summary>
/// <param name="warningsByProject">Dictionary with what warnings are currently known (by project) that we will add to.</param>
/// <param name="buildEventContext">Context for the project to be added</param>
/// <param name="codes">Codes to add</param>
private void AddWarningsAsMessagesOrErrors(ref IDictionary<WarningsConfigKey, ISet<string>> warningsByProject, BuildEventContext buildEventContext, ISet<string> codes)
{
lock (_lockObject)
{
WarningsConfigKey key = GetWarningsConfigKey(buildEventContext);
warningsByProject ??= new ConcurrentDictionary<WarningsConfigKey, ISet<string>>();
if (!warningsByProject.ContainsKey(key))
{
// The same project instance can be built multiple times with different targets. In this case the codes have already been added
warningsByProject[key] = new HashSet<string>(codes, StringComparer.OrdinalIgnoreCase);
}
}
}
/// <summary>
/// Return an array which contains the logger type names
/// this can be used to display which loggers are registered on the node
/// </summary>
public ICollection<string> RegisteredLoggerTypeNames
{
get
{
lock (_lockObject)
{
if (_loggers == null)
{
return null;
}
List<string> loggerTypes = new List<string>();
foreach (ILogger logger in _loggers)
{
loggerTypes.Add(logger.GetType().FullName);
}
return loggerTypes;
}
}
}
/// <summary>
/// Return an array which contains the sink names
/// this can be used to display which sinks are on the node
/// </summary>
public ICollection<string> RegisteredSinkNames
{
get
{
lock (_lockObject)
{
if (_eventSinkDictionary == null)
{
return null;
}
List<string> eventSinkNames = new List<string>();
foreach (KeyValuePair<int, IBuildEventSink> kvp in _eventSinkDictionary)
{
eventSinkNames.Add(kvp.Value.Name);
}
return eventSinkNames;
}
}
}
/// <summary>
/// Returns the minimum logging importance that must be logged because there is a possibility that
/// at least one registered logger consumes it.
/// </summary>
public MessageImportance MinimumRequiredMessageImportance
{
get
{
// If we haven't set the field return the default of "all messages must be logged".
return _minimumRequiredMessageImportance ?? MessageImportance.Low;
}
}
#endregion
#region Members
#region Public methods
/// <summary>
/// Create an instance of a LoggingService using the specified mode.
/// This method is used by the object factories to create instances of components.
/// </summary>
/// <param name="mode">Should the logger component created be synchronous or asynchronous</param>
/// <param name="node">The identifier of the node.</param>
/// <returns>An instantiated LoggingService as a IBuildComponent</returns>
public static ILoggingService CreateLoggingService(LoggerMode mode, int node)
{
return new LoggingService(mode, node);
}
/// <summary>
/// NotThreadSafe, this method should only be called from the component host thread
/// Called by the build component host when a component is first initialized.
/// </summary>
/// <param name="buildComponentHost">The component host for this object</param>
/// <exception cref="InternalErrorException">When buildComponentHost is null</exception>
/// <exception cref="InternalErrorException">Service has already shutdown</exception>
public void InitializeComponent(IBuildComponentHost buildComponentHost)
{
lock (_lockObject)
{
ErrorUtilities.VerifyThrow(_serviceState != LoggingServiceState.Shutdown, " The object is shutdown, should not do any operations on a shutdown component");
ErrorUtilities.VerifyThrow(buildComponentHost != null, "BuildComponentHost was null");
_componentHost = buildComponentHost;
// Get the number of initial nodes the host is running with, if the component host does not have
// this information default to 1
_maxCPUCount = buildComponentHost.BuildParameters.MaxNodeCount;
Question = buildComponentHost.BuildParameters.Question;
// Ask the component host if onlyLogCriticalEvents is true or false. If the host does
// not have this information default to false.
_onlyLogCriticalEvents = buildComponentHost.BuildParameters.OnlyLogCriticalEvents;
_serviceState = LoggingServiceState.Initialized;
}
}
/// <summary>
/// NotThreadSafe, this method should only be called from the component host thread
/// Called by the build component host when the component host is about to shutdown.
/// 1. Shutdown forwarding loggers so that any events they have left to forward can get into the queue
/// 2. Terminate the logging thread
/// 3. Null out sinks and the filter event source so that no more events can get to the central loggers
/// 4. Shutdown the central loggers
/// </summary>
/// <exception cref="InternalErrorException">Service has already shutdown</exception>
/// <exception cref="LoggerException"> A logger may throw a logger exception when shutting down</exception>
/// <exception cref="InternalLoggerException">A logger will wrap other exceptions (except ExceptionHandling.IsCriticalException exceptions) in a InternalLoggerException if it crashes during shutdown</exception>
public void ShutdownComponent()
{
lock (_lockObject)
{
ErrorUtilities.VerifyThrow(_serviceState != LoggingServiceState.Shutdown, " The object is shutdown, should not do any operations on a shutdown component");
// Set the state to indicate we are starting the shutdown process.
_serviceState = LoggingServiceState.ShuttingDown;
try
{
try
{
// 1. Shutdown forwarding loggers so that any events they have left to forward can get into the queue
foreach (ILogger logger in _loggers)
{
if (logger is IForwardingLogger)
{
ShutdownLogger(logger);
}
}
}
finally
{
// 2. Terminate the logging event queue
if (_logMode == LoggerMode.Asynchronous)
{
TerminateLoggingEventProcessing();
}
}
// 3. Null out sinks and the filter event source so that no more events can get to the central loggers
_filterEventSource?.ShutDown();
foreach (IBuildEventSink sink in _eventSinkDictionary.Values)
{
sink.ShutDown();
}
// 4. Shutdown the central loggers
foreach (ILogger logger in _loggers)
{
ShutdownLogger(logger);
}
}
finally
{
// Revert the centralLogger sinId back to -1 so that when another central logger is registered, it will generate a new
// sink for the central loggers.
_centralForwardingLoggerSinkId = -1;
CleanLoggingEventProcessing();
_loggers = new List<ILogger>();
_loggerDescriptions = null;
_eventSinkDictionary = null;
_filterEventSource = null;
_serviceState = LoggingServiceState.Shutdown;
}
}
}
/// <summary>
/// Will receive a logging packet and send it to the correct
/// sink which is registered to the LoggingServices.
/// PacketReceived should be called from a single thread.
/// </summary>
/// <param name="node">The node from which the packet was received.</param>
/// <param name="packet">A LogMessagePacket</param>
/// <exception cref="InternalErrorException">Packet is null</exception>
/// <exception cref="InternalErrorException">Packet is not a NodePacketType.LogMessage</exception>
public void PacketReceived(int node, INodePacket packet)
{
// The packet cannot be null
ErrorUtilities.VerifyThrow(packet != null, "packet was null");
// Expected the packet type to be a logging message packet
// PERF: Not using VerifyThrow to avoid allocations for enum.ToString (boxing of NodePacketType) in the non-error case.
if (packet.Type != NodePacketType.LogMessage)
{
ErrorUtilities.ThrowInternalError("Expected packet type \"{0}\" but instead got packet type \"{1}\".", nameof(NodePacketType.LogMessage), packet.Type.ToString());
}
LogMessagePacket loggingPacket = (LogMessagePacket)packet;
InjectNonSerializedData(loggingPacket);
WarnOnDeprecatedCustomArgsSerialization(loggingPacket);
ProcessLoggingEvent(loggingPacket.NodeBuildEvent);
}
/// <summary>
/// Serializing unknown CustomEvent which has to use unsecure BinaryFormatter by TranslateDotNet.
/// Since BinaryFormatter is going to be deprecated, log warning so users can use new Extended*EventArgs instead of custom
/// EventArgs derived from existing EventArgs.
/// </summary>
private void WarnOnDeprecatedCustomArgsSerialization(LogMessagePacket loggingPacket)
{
if (loggingPacket.EventType == LoggingEventType.CustomEvent
&& ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_8)
&& Traits.Instance.EscapeHatches.EnableWarningOnCustomBuildEvent)
{
BuildEventArgs buildEvent = loggingPacket.NodeBuildEvent.Value.Value;
BuildEventContext buildEventContext = buildEvent?.BuildEventContext ?? BuildEventContext.Invalid;
string message = ResourceUtilities.FormatResourceStringStripCodeAndKeyword(
out string warningCode,
out string helpKeyword,
"DeprecatedEventSerialization",
buildEvent?.GetType().Name ?? string.Empty);
BuildWarningEventArgs warning = new(
null,
warningCode,
BuildEventFileInfo.Empty.File,
BuildEventFileInfo.Empty.Line,
BuildEventFileInfo.Empty.Column,
BuildEventFileInfo.Empty.EndLine,
BuildEventFileInfo.Empty.EndColumn,
message,
helpKeyword,
"MSBuild");
warning.BuildEventContext = buildEventContext;
if (warning.ProjectFile == null && buildEventContext.ProjectContextId != BuildEventContext.InvalidProjectContextId)
{
warning.ProjectFile = buildEvent switch
{
BuildMessageEventArgs buildMessageEvent => buildMessageEvent.ProjectFile,
BuildErrorEventArgs buildErrorEvent => buildErrorEvent.ProjectFile,
BuildWarningEventArgs buildWarningEvent => buildWarningEvent.ProjectFile,
_ => null,
};
}
ProcessLoggingEvent(warning);
}
}
/// <summary>
/// Register an instantiated logger which implements the ILogger interface. This logger will be registered to a specific event
/// source (the central logger event source) which will receive all logging messages for a given build.
/// This should not be used on a node, Loggers are not to be registered on a child node.
/// </summary>
/// <param name="logger">ILogger</param>
/// <returns>True if the logger has been registered successfully. False if the logger was not registered due to it already being registered before</returns>
/// <exception cref="InternalErrorException">If logger is null</exception>
public bool RegisterLogger(ILogger logger)
{
lock (_lockObject)
{
ErrorUtilities.VerifyThrow(_serviceState != LoggingServiceState.Shutdown, " The object is shutdown, should not do any operations on a shutdown component");
ErrorUtilities.VerifyThrow(logger != null, "logger was null");
// If the logger is already in the list it should not be registered again.
if (_loggers.Contains(logger))
{
return false;
}
// If we have not created a distributed logger to forward all events to the central loggers and
// a sink which will consume the events and send them to each of the central loggers, we
// should do that now
if (_centralForwardingLoggerSinkId == -1)
{
// Create a forwarding logger which forwards all events to an eventSourceSink
Assembly engineAssembly = typeof(LoggingService).GetTypeInfo().Assembly;
string loggerClassName = "Microsoft.Build.BackEnd.Logging.CentralForwardingLogger";
string loggerAssemblyName = engineAssembly.GetName().FullName;
LoggerDescription centralForwardingLoggerDescription = new LoggerDescription(
loggerClassName,
loggerAssemblyName,
null /*Not needed as we are loading from current assembly*/,
string.Empty /*No parameters needed as we are forwarding all events*/,