File: System\Diagnostics\TraceSource.cs
Web Access
Project: src\src\libraries\System.Diagnostics.TraceSource\src\System.Diagnostics.TraceSource.csproj (System.Diagnostics.TraceSource)
// 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;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
 
namespace System.Diagnostics
{
    public class TraceSource
    {
        private static readonly List<WeakReference<TraceSource>> s_tracesources = new List<WeakReference<TraceSource>>();
        private static int s_LastCollectionCount;
 
        private volatile SourceSwitch? _internalSwitch;
        private volatile TraceListenerCollection? _listeners;
        private readonly SourceLevels _switchLevel;
        private readonly string _sourceName;
        internal volatile bool _initCalled;   // Whether we've called Initialize already.
        internal volatile bool _configInitializing;
        private StringDictionary? _attributes;
 
        public TraceSource(string name) : this(name, SourceLevels.Off) { }
 
        public TraceSource(string name, SourceLevels defaultLevel)
        {
            ArgumentException.ThrowIfNullOrEmpty(name);
 
            _sourceName = name;
            _switchLevel = defaultLevel;
 
            // Add a weakreference to this source and cleanup invalid references
            lock (s_tracesources)
            {
                _pruneCachedTraceSources();
                s_tracesources.Add(new WeakReference<TraceSource>(this));
            }
        }
 
        private static void _pruneCachedTraceSources()
        {
            lock (s_tracesources)
            {
                if (s_LastCollectionCount != GC.CollectionCount(2))
                {
                    List<WeakReference<TraceSource>> buffer = new List<WeakReference<TraceSource>>(s_tracesources.Count);
                    for (int i = 0; i < s_tracesources.Count; i++)
                    {
                        if (s_tracesources[i].TryGetTarget(out _))
                        {
                            buffer.Add(s_tracesources[i]);
                        }
                    }
                    if (buffer.Count < s_tracesources.Count)
                    {
                        s_tracesources.Clear();
                        s_tracesources.AddRange(buffer);
                        s_tracesources.TrimExcess();
                    }
                    s_LastCollectionCount = GC.CollectionCount(2);
                }
            }
        }
 
        private void Initialize()
        {
            if (!_initCalled)
            {
                lock (this)
                {
                    if (_initCalled)
                        return;
 
                    if (_configInitializing)
                        return;
 
                    _configInitializing = true;
 
                    NoConfigInit_BeforeEvent();
 
                    InitializingTraceSourceEventArgs e = new InitializingTraceSourceEventArgs(this);
                    OnInitializing(e);
 
                    if (!e.WasInitialized)
                    {
                        NoConfigInit_AfterEvent();
                    }
 
                    _configInitializing = false;
                    _initCalled = true;
                }
            }
 
            void NoConfigInit_BeforeEvent()
            {
                _listeners = new TraceListenerCollection();
                _internalSwitch = new SourceSwitch(_sourceName, _switchLevel.ToString());
            }
 
            void NoConfigInit_AfterEvent()
            {
                Debug.Assert(_listeners != null);
                _listeners.Add(new DefaultTraceListener());
            }
        }
 
        public void Close()
        {
            // No need to call Initialize()
            if (_listeners != null)
            {
                // Use global lock
                lock (TraceInternal.critSec)
                {
                    foreach (TraceListener? listener in _listeners)
                    {
                        listener!.Close();
                    }
                }
            }
        }
 
        public void Flush()
        {
            // No need to call Initialize()
            if (_listeners != null)
            {
                if (TraceInternal.UseGlobalLock)
                {
                    lock (TraceInternal.critSec)
                    {
                        foreach (TraceListener? listener in _listeners)
                        {
                            listener!.Flush();
                        }
                    }
                }
                else
                {
                    foreach (TraceListener? listener in _listeners)
                    {
                        if (!listener!.IsThreadSafe)
                        {
                            lock (listener)
                            {
                                listener.Flush();
                            }
                        }
                        else
                        {
                            listener.Flush();
                        }
                    }
                }
            }
        }
 
        protected internal virtual string[]? GetSupportedAttributes() => null;
 
        internal static void RefreshAll()
        {
            lock (s_tracesources)
            {
                _pruneCachedTraceSources();
                for (int i = 0; i < s_tracesources.Count; i++)
                {
                    if (s_tracesources[i].TryGetTarget(out TraceSource? tracesource))
                    {
                        tracesource.Refresh();
                    }
                }
            }
        }
 
        internal void Refresh()
        {
            if (!_initCalled)
            {
                Initialize();
                return;
            }
 
            OnInitializing(new InitializingTraceSourceEventArgs(this));
        }
 
        [Conditional("TRACE")]
        public void TraceEvent(TraceEventType eventType, int id)
        {
            Initialize();
 
            if (_internalSwitch!.ShouldTrace(eventType) && _listeners != null)
            {
                TraceEventCache manager = new TraceEventCache();
 
                if (TraceInternal.UseGlobalLock)
                {
                    // we lock on the same object that Trace does because we're writing to the same Listeners.
                    lock (TraceInternal.critSec)
                    {
                        for (int i = 0; i < _listeners.Count; i++)
                        {
                            TraceListener listener = _listeners[i];
                            listener.TraceEvent(manager, Name, eventType, id);
                            if (Trace.AutoFlush) listener.Flush();
                        }
                    }
                }
                else
                {
                    for (int i = 0; i < _listeners.Count; i++)
                    {
                        TraceListener listener = _listeners[i];
                        if (!listener.IsThreadSafe)
                        {
                            lock (listener)
                            {
                                listener.TraceEvent(manager, Name, eventType, id);
                                if (Trace.AutoFlush) listener.Flush();
                            }
                        }
                        else
                        {
                            listener.TraceEvent(manager, Name, eventType, id);
                            if (Trace.AutoFlush) listener.Flush();
                        }
                    }
                }
            }
        }
 
        [Conditional("TRACE")]
        public void TraceEvent(TraceEventType eventType, int id, string? message)
        {
            Initialize();
 
            if (_internalSwitch!.ShouldTrace(eventType) && _listeners != null)
            {
                TraceEventCache manager = new TraceEventCache();
 
                if (TraceInternal.UseGlobalLock)
                {
                    // we lock on the same object that Trace does because we're writing to the same Listeners.
                    lock (TraceInternal.critSec)
                    {
                        for (int i = 0; i < _listeners.Count; i++)
                        {
                            TraceListener listener = _listeners[i];
                            listener.TraceEvent(manager, Name, eventType, id, message);
                            if (Trace.AutoFlush) listener.Flush();
                        }
                    }
                }
                else
                {
                    for (int i = 0; i < _listeners.Count; i++)
                    {
                        TraceListener listener = _listeners[i];
                        if (!listener.IsThreadSafe)
                        {
                            lock (listener)
                            {
                                listener.TraceEvent(manager, Name, eventType, id, message);
                                if (Trace.AutoFlush) listener.Flush();
                            }
                        }
                        else
                        {
                            listener.TraceEvent(manager, Name, eventType, id, message);
                            if (Trace.AutoFlush) listener.Flush();
                        }
                    }
                }
            }
        }
 
        [Conditional("TRACE")]
        public void TraceEvent(TraceEventType eventType, int id, [StringSyntax(StringSyntaxAttribute.CompositeFormat)] string? format, params object?[]? args)
        {
            Initialize();
 
            if (_internalSwitch!.ShouldTrace(eventType) && _listeners != null)
            {
                TraceEventCache manager = new TraceEventCache();
 
                if (TraceInternal.UseGlobalLock)
                {
                    // we lock on the same object that Trace does because we're writing to the same Listeners.
                    lock (TraceInternal.critSec)
                    {
                        for (int i = 0; i < _listeners.Count; i++)
                        {
                            TraceListener listener = _listeners[i];
                            listener.TraceEvent(manager, Name, eventType, id, format, args);
                            if (Trace.AutoFlush) listener.Flush();
                        }
                    }
                }
                else
                {
                    for (int i = 0; i < _listeners.Count; i++)
                    {
                        TraceListener listener = _listeners[i];
                        if (!listener.IsThreadSafe)
                        {
                            lock (listener)
                            {
                                listener.TraceEvent(manager, Name, eventType, id, format, args);
                                if (Trace.AutoFlush) listener.Flush();
                            }
                        }
                        else
                        {
                            listener.TraceEvent(manager, Name, eventType, id, format, args);
                            if (Trace.AutoFlush) listener.Flush();
                        }
                    }
                }
            }
        }
 
        [Conditional("TRACE")]
        public void TraceData(TraceEventType eventType, int id, object? data)
        {
            Initialize();
 
            if (_internalSwitch!.ShouldTrace(eventType) && _listeners != null)
            {
                TraceEventCache manager = new TraceEventCache();
 
                if (TraceInternal.UseGlobalLock)
                {
                    // we lock on the same object that Trace does because we're writing to the same Listeners.
                    lock (TraceInternal.critSec)
                    {
                        for (int i = 0; i < _listeners.Count; i++)
                        {
                            TraceListener listener = _listeners[i];
                            listener.TraceData(manager, Name, eventType, id, data);
                            if (Trace.AutoFlush) listener.Flush();
                        }
                    }
                }
                else
                {
                    for (int i = 0; i < _listeners.Count; i++)
                    {
                        TraceListener listener = _listeners[i];
                        if (!listener.IsThreadSafe)
                        {
                            lock (listener)
                            {
                                listener.TraceData(manager, Name, eventType, id, data);
                                if (Trace.AutoFlush) listener.Flush();
                            }
                        }
                        else
                        {
                            listener.TraceData(manager, Name, eventType, id, data);
                            if (Trace.AutoFlush) listener.Flush();
                        }
                    }
                }
            }
        }
 
        [Conditional("TRACE")]
        public void TraceData(TraceEventType eventType, int id, params object?[]? data)
        {
            Initialize();
 
            if (_internalSwitch!.ShouldTrace(eventType) && _listeners != null)
            {
                TraceEventCache manager = new TraceEventCache();
 
                if (TraceInternal.UseGlobalLock)
                {
                    // we lock on the same object that Trace does because we're writing to the same Listeners.
                    lock (TraceInternal.critSec)
                    {
                        for (int i = 0; i < _listeners.Count; i++)
                        {
                            TraceListener listener = _listeners[i];
                            listener.TraceData(manager, Name, eventType, id, data);
                            if (Trace.AutoFlush) listener.Flush();
                        }
                    }
                }
                else
                {
                    for (int i = 0; i < _listeners.Count; i++)
                    {
                        TraceListener listener = _listeners[i];
                        if (!listener.IsThreadSafe)
                        {
                            lock (listener)
                            {
                                listener.TraceData(manager, Name, eventType, id, data);
                                if (Trace.AutoFlush) listener.Flush();
                            }
                        }
                        else
                        {
                            listener.TraceData(manager, Name, eventType, id, data);
                            if (Trace.AutoFlush) listener.Flush();
                        }
                    }
                }
            }
        }
 
        [Conditional("TRACE")]
        public void TraceInformation(string? message)
        { // eventType= TraceEventType.Info, id=0
            // No need to call Initialize()
            TraceEvent(TraceEventType.Information, 0, message, null);
        }
 
        [Conditional("TRACE")]
        public void TraceInformation([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string? format, params object?[]? args)
        {
            // No need to call Initialize()
            TraceEvent(TraceEventType.Information, 0, format, args);
        }
 
        [Conditional("TRACE")]
        public void TraceTransfer(int id, string? message, Guid relatedActivityId)
        {
            // Ensure that config is loaded
            Initialize();
 
            TraceEventCache manager = new TraceEventCache();
 
            if (_internalSwitch!.ShouldTrace(TraceEventType.Transfer) && _listeners != null)
            {
                if (TraceInternal.UseGlobalLock)
                {
                    // we lock on the same object that Trace does because we're writing to the same Listeners.
                    lock (TraceInternal.critSec)
                    {
                        for (int i = 0; i < _listeners.Count; i++)
                        {
                            TraceListener listener = _listeners[i];
                            listener.TraceTransfer(manager, Name, id, message, relatedActivityId);
 
                            if (Trace.AutoFlush)
                            {
                                listener.Flush();
                            }
                        }
                    }
                }
                else
                {
                    for (int i = 0; i < _listeners.Count; i++)
                    {
                        TraceListener listener = _listeners[i];
 
                        if (!listener.IsThreadSafe)
                        {
                            lock (listener)
                            {
                                listener.TraceTransfer(manager, Name, id, message, relatedActivityId);
                                if (Trace.AutoFlush)
                                {
                                    listener.Flush();
                                }
                            }
                        }
                        else
                        {
                            listener.TraceTransfer(manager, Name, id, message, relatedActivityId);
                            if (Trace.AutoFlush)
                            {
                                listener.Flush();
                            }
                        }
                    }
                }
            }
        }
 
        public StringDictionary Attributes
        {
            get
            {
                Initialize();
                return _attributes ??= new StringDictionary();
            }
        }
 
        /// <summary>
        /// The default level assigned in the constructor.
        /// </summary>
        public SourceLevels DefaultLevel => _switchLevel;
 
        /// <summary>
        ///  Occurs when a <see cref="TraceSource"/> needs to be initialized.
        /// </summary>
        public static event EventHandler<InitializingTraceSourceEventArgs>? Initializing;
 
        internal void OnInitializing(InitializingTraceSourceEventArgs e)
        {
            Initializing?.Invoke(this, e);
            TraceUtils.VerifyAttributes(Attributes, GetSupportedAttributes(), this);
 
            if (TraceInternal.UseGlobalLock)
            {
                // we lock on the same object that Trace does because we're writing to the same Listeners.
                lock (TraceInternal.critSec)
                {
                    foreach (TraceListener listener in Listeners)
                    {
                        TraceUtils.VerifyAttributes(listener.Attributes, listener.GetSupportedAttributes(), this);
                    }
                }
            }
            else
            {
                foreach (TraceListener listener in Listeners)
                {
                    if (!listener!.IsThreadSafe)
                    {
                        lock (listener)
                        {
                            TraceUtils.VerifyAttributes(listener.Attributes, listener.GetSupportedAttributes(), this);
                        }
                    }
                    else
                    {
                        TraceUtils.VerifyAttributes(listener.Attributes, listener.GetSupportedAttributes(), this);
                    }
                }
            }
        }
 
        public string Name => _sourceName;
 
        public TraceListenerCollection Listeners
        {
            get
            {
                Initialize();
                return _listeners!;
            }
        }
 
        public SourceSwitch Switch
        {
            // No need for security demand here. SourceSwitch.set_Level is protected already.
            get
            {
                Initialize();
                return _internalSwitch!;
            }
            set
            {
                ArgumentNullException.ThrowIfNull(value, nameof(Switch));
 
                Initialize();
                _internalSwitch = value;
            }
        }
    }
}