Aracılığıyla paylaş


EventSource Olayları Oluşturmak için Kod Enstrümantasyonu

Bu makale şunlar için geçerlidir: ✔️ .NET Core 3.1 ve sonraki sürümleri .✔️ NET Framework 4.5 ve sonraki sürümleri

Başlarken kılavuzu size basit bir EventSource oluşturmayı ve izleme dosyasındaki olayları toplamayı gösterdi. Bu öğreticide, System.Diagnostics.Tracing.EventSourcekullanarak olay oluşturma hakkında daha fazla ayrıntıya gider.

Minimal bir EventSource

[EventSource(Name = "Demo")]
class DemoEventSource : EventSource
{
    public static DemoEventSource Log { get; } = new DemoEventSource();

    [Event(1)]
    public void AppStarted(string message, int favoriteNumber) => WriteEvent(1, message, favoriteNumber);
}

Türetilmiş bir EventSource'un temel yapısı her zaman aynıdır. Özellikle:

  • Sınıf, System.Diagnostics.Tracing.EventSource'dan miras alır.
  • Oluşturmak istediğiniz her farklı olay türü için bir yöntemin tanımlanması gerekir. Bu yöntem, oluşturulan olayın adı kullanılarak adlandırılmalıdır. Olayda ek veriler varsa, bunlar argümanlar kullanılarak aktarılmalıdır. Yalnızca belirli türlere izin verildiğinden bu olay bağımsız değişkenlerinin serileştirilmesi gerekir.
  • Her yöntemin bir gövdesi vardır ve bu gövde, WriteEvent'i bir kimlik (olayı temsil eden sayısal bir değer) ve olay yönteminin bağımsız değişkenlerini ileterek çağırır. Kimliğin EventSource içinde benzersiz olması gerekir. Kimlik açıkça System.Diagnostics.Tracing.EventAttribute kullanılarak atanır
  • EventSources tekil örnekler olarak tasarlanmıştır. Bu nedenle, bu tekil değişkeni temsil eden Logadlı kurala göre statik bir değişken tanımlamak uygundur.

Olay yöntemlerini tanımlama kuralları

  1. EventSource sınıfında tanımlanan sanal olmayan, geçersiz dönüş yöntemi varsayılan olarak bir olay günlüğü yöntemidir.
  2. Sanal veya boş olmayan dönüş yapan yöntemler yalnızca System.Diagnostics.Tracing.EventAttribute ile işaretlenmişse eklenir.
  3. Uygun bir yöntemi günlüğe kaydedilmeyen olarak işaretlemek için, onu System.Diagnostics.Tracing.NonEventAttribute ile işaretlemeniz gerekir.
  4. Olay kaydetme yöntemleri olay kimlikleriyle ilişkilidir. Bu, yöntemi System.Diagnostics.Tracing.EventAttribute ile açıkça işaretleyerek veya dolaylı olarak sınıftaki yöntemin sıralı numarasıyla yapılabilir. Örneğin, sınıfta örtük numaralandırma kullanarak ilk yöntemin ID numarası 1, ikincisinin ID numarası 2 olarak atanır ve devam eder.
  5. Olay günlüğü yöntemleri bir WriteEvent, WriteEventCoreWriteEventWithRelatedActivityId veya WriteEventWithRelatedActivityIdCore aşırı yükleme çağırmalıdır.
  6. Etkinlik kimliği (örtülü veya açık), çağırdığı WriteEvent* API'sine geçirilen ilk bağımsız değişkenle eşleşmelidir.
  7. EventSource yöntemine geçirilen bağımsız değişkenlerin sayısı, türleri ve sırası, WriteEvent* API'lerine geçirilmeleri ile uyumlu olmalıdır. WriteEvent için bağımsız değişkenler Olay Kimliği'ni izler, WriteEventWithRelatedActivityId için bağımsız değişkenler ilgiliAktiviteKimliği'ni izler. WriteEvent*Core yöntemleri için argümanlar, data parametreye el ile serileştirilmelidir.
  8. Olay adları < veya > karakter içeremez. Kullanıcı tanımlı yöntemler de bu karakterleri içeremese de, async yöntemleri derleyici tarafından bunları içerecek şekilde yeniden yazılır. Bu üretilen yöntemlerin olay haline gelmediğinden emin olmak için, bir EventSource üzerindeki tüm olay dışı yöntemleri NonEventAttributeile işaretleyin.

En iyi yöntemler

  1. EventSource'tan türetilen türlerin genellikle hiyerarşide ara türleri yoktur veya arabirimleri uygulamaz. Bunun yararlı olabileceği bazı özel durumlar için aşağıdaki gelişmiş özelleştirmeler bakın.
  2. EventSource sınıfının adı genellikle EventSource için kötü bir genel addır. Günlük yapılandırmalarında ve günlük görüntüleyicilerinde gösterilecek genel adlar, dünya genelinde benzersiz olmalıdır. Bu nedenle, System.Diagnostics.Tracing.EventSourceAttributekullanarak EventSource'unuza genel bir ad vermek iyi bir uygulamadır. Yukarıda kullanılan "Demo" adı kısadır ve benzersiz olma olasılığı düşüktür, bu nedenle üretim kullanımı için iyi bir seçim değildir. Yaygın bir kural, "MyCompany-Samples-Demo" gibi bir ayırıcı olarak . veya - ile hiyerarşik bir ad kullanmak ya da EventSource'un olayları sağladığı Derlemenin veya ad alanının adını kullanmaktır. Ortak adın bir parçası olarak "EventSource" eklenmesi önerilmez.
  3. Olay Kimliklerini açıkça atayın; bu şekilde kaynak sınıftaki kodda yeniden düzenleme veya ortaya bir yöntem ekleme gibi zararsız görünen değişiklikler her yöntemle ilişkili olay kimliğini değiştirmez.
  4. Bir çalışma biriminin başlangıcını ve sonunu temsil eden olaylar yazarken, kural gereği bu yöntemler 'Başlat' ve 'Durdur' sonekleri ile adlandırılır. Örneğin, 'RequestStart' ve 'RequestStop'.
  5. Geriye dönük uyumluluk nedeniyle gerekmedikçe EventSourceAttribute Guid özelliği için açık bir değer belirtmeyin. Varsayılan Guid değeri kaynağın adından türetilir. Bu değer, araçların daha fazla insan tarafından okunabilir adı kabul etmesini ve aynı Guid'yi türetmesini sağlar.
  6. Bir olayı tetiklemeyle ilgili yoğun kaynak gerektiren herhangi bir çalışma gerçekleştirmeden önce, örneğin olay devre dışı bırakıldığında gerekmeyecek pahalı bir olay bağımsız değişkeni hesaplama gibi, IsEnabled()'ı arayın.
  7. EventSource nesnesini yeniden uyumlu tutmaya ve uygun şekilde sürüme eklemeye çalış. Bir olayın varsayılan sürümü 0'dır. sürüm, EventAttribute.Versionayarlanarak değiştirilebilir. Olayla seri hale getirilmiş olan veriyi her değiştirdiğinizde olayın versiyonunu değiştirin. Olay bildiriminin sonuna, yani yöntem parametreleri listesinin sonuna her zaman yeni serileştirilmiş veriler ekleyin. Bu mümkün değilse, eskisini değiştirmek için yeni bir kimlikle yeni bir olay oluşturun.
  8. Olay yöntemleri bildirilirken, değişken boyuttaki veriler öncesinde sabit boyutlu yük verilerini belirtin.
  9. Null karakter içeren dizeleri kullanmayın. ETW EventSource için bildirim üretilirken, tüm dizgelerin null karakterle sonlandırılmış olduğunu belirtecek, ancak C# dizgesinde null karakter bulunabilse de. Bir dize null karakter içeriyorsa, dizenin tamamı olay yüküne yazılır, ancak herhangi bir ayrıştırıcı ilk null karakteri dizenin sonu olarak kabul eder. Dizeden sonra yük bağımsız değişkenleri varsa, dizenin geri kalanı hedeflenen değer yerine ayrıştırılır.

Tipik olay özelleştirmeleri

Olay ayrıntı düzeylerini ayarlama

Her olayın ayrıntı düzeyi vardır ve olay aboneleri genellikle bir EventSource üzerindeki tüm olayları belirli bir ayrıntı düzeyine kadar etkinleştirir. Olaylar, Level özelliğini kullanarak ayrıntı düzeylerini bildirir. Örneğin, aşağıdaki EventSource'ta, Informational ve altındaki düzeydeki olayları talep eden bir abone, Verbose DebugMessage olayını günlüğe kaydetmez.

[EventSource(Name = "MyCompany-Samples-Demo")]
class DemoEventSource : EventSource
{
    public static DemoEventSource Log { get; } = new DemoEventSource();

    [Event(1, Level = EventLevel.Informational)]
    public void AppStarted(string message, int favoriteNumber) => WriteEvent(1, message, favoriteNumber);
    [Event(2, Level = EventLevel.Verbose)]
    public void DebugMessage(string message) => WriteEvent(2, message);
}

Bir olayın ayrıntı düzeyi EventAttribute'ta belirtilmezse, varsayılan olarak Informational olur.

En iyi yöntem

Görece nadir uyarılar veya hatalar için Bilgi düzeyinden küçük düzeyler kullanın. Şüpheye düştüğünüzde, Varsayılan Bilgi'yi kullanın ve 1000 olay/sn'den daha sık gerçekleşen olaylar için Ayrıntılı'yı kullanın.

Olay anahtar sözcüklerini ayarlama

Bazı olay izleme sistemleri ek bir filtreleme mekanizması olarak anahtar sözcükleri destekler. Olayları ayrıntı düzeyine göre kategorilere ayıran ayrıntıdan farklı olarak, anahtar sözcükler olayları kod işlevselliği alanları gibi diğer ölçütlere göre kategorilere ayırmaya yöneliktir veya belirli sorunları tanılamak için yararlı olabilir. Anahtar sözcükler bit bayrakları olarak adlandırılır ve her olayda herhangi bir anahtar sözcük bileşimi uygulanabilir. Örneğin aşağıdaki EventSource, istek işlemeyle ilgili bazı olayları ve başlatmayla ilgili diğer olayları tanımlar. Bir geliştirici başlatma performansını analiz etmek isterse yalnızca başlangıç anahtar sözcüğüyle işaretlenmiş olayların günlüğe kaydedilmesini etkinleştirebilir.

[EventSource(Name = "Demo")]
class DemoEventSource : EventSource
{
    public static DemoEventSource Log { get; } = new DemoEventSource();

    [Event(1, Keywords = Keywords.Startup)]
    public void AppStarted(string message, int favoriteNumber) => WriteEvent(1, message, favoriteNumber);
    [Event(2, Keywords = Keywords.Requests)]
    public void RequestStart(int requestId) => WriteEvent(2, requestId);
    [Event(3, Keywords = Keywords.Requests)]
    public void RequestStop(int requestId) => WriteEvent(3, requestId);

    public class Keywords   // This is a bitvector
    {
        public const EventKeywords Startup = (EventKeywords)0x0001;
        public const EventKeywords Requests = (EventKeywords)0x0002;
    }
}

Anahtar sözcükler Keywords adlı iç içe geçmiş bir sınıf kullanılarak tanımlanmalıdır ve her anahtar sözcük public const EventKeywordstürüne sahip bir üye tarafından tanımlanır.

En iyi yöntem

Yüksek hacimli olaylar arasında ayrım yaparken anahtar sözcükler daha önemlidir. Bu, olay tüketicisinin ayrıntı düzeyini üst düzeye çıkarmasına, ancak yalnızca olayların dar alt kümelerini etkinleştirerek performans ek yükünü ve günlük boyutunu yönetmesine olanak tanır. 1.000/sn'den fazla tetiklenen olaylar, benzersiz bir anahtar sözcük için iyi adaylardır.

Desteklenen parametre türleri

EventSource, yalnızca sınırlı bir tür kümesini kabul edebilmesi için tüm olay parametrelerinin seri hale getirilebilmesini gerektirir. Bunlar şunlardır:

  • Primitifler: bool, byte, sbyte, char, short, ushort, int, uint, long, ulong, float, double, IntPtr ve UIntPtr, Guid, decimal, string, DateTime, DateTimeOffset, TimeSpan
  • Enumlar
  • System.Diagnostics.Tracing.EventDataAttributeile özniteliklendirilen yapılar. Yalnızca seri hale getirilebilir türleri olan genel örnek özellikleri serileştirilir.
  • Tüm ortak özelliklerin serileştirilebilir türler olduğu anonim türler
  • Serileştirilebilir tür dizileri
  • Null atanabilir<T> burada T serileştirilebilir bir türdür
  • KeyValuePair<T, U> burada hem T hem de U seri hale getirilebilir türlerdir
  • Tam olarak bir T türü için IEnumerable<T> uygulayan türler ve burada T serileştirilebilir bir türdür

Sorun giderme

EventSource sınıfı, varsayılan olarak hiçbir zaman özel durum oluşturmaması için tasarlanmıştır. Bu özellik kullanışlıdır çünkü günlükler genellikle isteğe bağlı görülür ve bir günlük mesajı yazarken oluşabilecek bir hata nedeniyle uygulamanızın başarısız olmasını genellikle istemezsiniz. Ancak bu, EventSource'unuzda herhangi bir hata bulmanızı zorlaştırır. Sorun gidermeye yardımcı olabilecek çeşitli teknikler şunlardır:

  1. EventSource oluşturucusunun EventSourceSettings'u parametre olarak alan aşırı yüklemeleri vardır. ThrowOnEventWriteErrors bayrağını geçici olarak etkinleştirmeyi deneyin.
  2. EventSource.ConstructionException özelliği, olay günlüğü yöntemleri doğrulanırken oluşturulan tüm Özel Durumları depolar. Bu, çeşitli yazma hatalarını ortaya çıkarabilir.
  3. EventSource, olay kimliği 0 kullanarak hataları günlüğe kaydeder ve bu hata olayı, hata ile ilgili açıklayıcı bir dize içerir.
  4. Hata ayıklama sırasında aynı hata dizesi Debug.WriteLine() kullanılarak da günlüğe kaydedilir ve hata ayıklama çıktı penceresinde gösterilir.
  5. EventSource, hatalar oluştuğunda dahili olarak özel durumlar oluşturur ve yakalar. Bu özel durumların ne zaman oluştuğunu gözlemlemek için hata ayıklayıcıda ilk şans özel durumlarını etkinleştirin veya .NET çalışma zamanının Özel durum olayları etkinken olay izlemeyi kullanın.

Gelişmiş özelleştirmeler

OpCode'ları ve Görevleri Ayarlama

ETW, olayları etiketlemeye ve filtrelemeye yönelik diğer mekanizmalar olan Görevler ve opcodeskavramlarına sahiptir. Task ve Opcode özelliklerini kullanarak olayları belirli görevler ve opcode'larla ilişkilendirebilirsiniz. İşte bir örnek:

[EventSource(Name = "Samples-EventSourceDemos-Customized")]
public sealed class CustomizedEventSource : EventSource
{
    static public CustomizedEventSource Log { get; } = new CustomizedEventSource();

    [Event(1, Task = Tasks.Request, Opcode=EventOpcode.Start)]
    public void RequestStart(int RequestID, string Url)
    {
        WriteEvent(1, RequestID, Url);
    }

    [Event(2, Task = Tasks.Request, Opcode=EventOpcode.Info)]
    public void RequestPhase(int RequestID, string PhaseName)
    {
        WriteEvent(2, RequestID, PhaseName);
    }

    [Event(3, Keywords = Keywords.Requests,
           Task = Tasks.Request, Opcode=EventOpcode.Stop)]
    public void RequestStop(int RequestID)
    {
        WriteEvent(3, RequestID);
    }

    public class Tasks
    {
        public const EventTask Request = (EventTask)0x1;
    }
}

<EventName>Start ve <EventName>Stop adlandırma desenine sahip, ardışık olay kimliklerine sahip iki olay yöntemi belirterek EventTask nesnelerini örtük olarak oluşturabilirsiniz. Bu olaylar sınıf tanımında yan yana bildirilmelidir ve <EventName>Start yöntemi önce gelmelidir.

Kendini açıklayan (izleme) ve bildirim olayı biçimleri karşılaştırması

Bu kavram yalnızca ETW'den EventSource'a abone olurken önemlidir. ETW'nin olayları günlüğe kaydetmenin iki farklı yolu vardır: bildirim biçimi ve kendini açıklayan (bazen izleme olarak da adlandırılır) biçimi. Bildirim tabanlı EventSource nesneleri, başlatma sonrasında sınıfında tanımlanan olayları temsil eden bir XML belgesi oluşturur ve günlüğe kaydeder. Bunun için EventSource'un sağlayıcıyı ve olay meta verilerini oluşturmak için kendisini yansıtması gerekir. Her bir olay için, kendini açıklayan biçimindeki meta veriler, olay verileriyle birlikte doğrudan iletilir, önceden değil. Kendi kendini açıklayan yaklaşım, önceden tanımlanmış bir olay günlüğü yöntemi oluşturmadan rastgele olaylar gönderebilen daha esnek Write yöntemlerini destekler. Ayrıca, erken yansımadan kaçındığı için başlangıçta biraz daha hızlıdır. Ancak her olayla birlikte yayılan ek meta veriler, yüksek hacimli olaylar gönderirken istenmeyebilecek küçük bir performans yükü ekler.

Kendini açıklayan olay biçimini kullanmak için EventSource(String) oluşturucusunu, EventSource(String, EventSourceSettings) oluşturucusunu kullanarak veya EventSourceSettings üzerinde EtwSelfDescribingEventFormat bayrağını ayarlayarak EventSource'unuzu oluşturun.

Arabirimleri uygulayan EventSource türleri

EventSource türü, ortak bir günlük hedefi tanımlamak için arabirimleri kullanan çeşitli gelişmiş günlük sistemleriyle sorunsuz bir şekilde tümleştirmek için bir arabirim uygulayabilir. Olası bir kullanım örneği aşağıda verilmişti:

public interface IMyLogging
{
    void Error(int errorCode, string msg);
    void Warning(string msg);
}

[EventSource(Name = "Samples-EventSourceDemos-MyComponentLogging")]
public sealed class MyLoggingEventSource : EventSource, IMyLogging
{
    public static MyLoggingEventSource Log { get; } = new MyLoggingEventSource();

    [Event(1)]
    public void Error(int errorCode, string msg)
    { WriteEvent(1, errorCode, msg); }

    [Event(2)]
    public void Warning(string msg)
    { WriteEvent(2, msg); }
}

Arabirim yöntemlerinde EventAttribute belirtmelisiniz, aksi takdirde (uyumluluk nedeniyle) yöntem günlük yöntemi olarak değerlendirilmez. Adlandırma çakışmalarını önlemek için açık arabirim yöntemi uygulamasına izin verilmez.

EventSource sınıf hiyerarşileri

Çoğu durumda, EventSource sınıfından doğrudan türetilen türler yazabilirsiniz. Ancak bazen özelleştirilmiş WriteEvent aşırı yüklemeleri gibi birden çok türetilmiş EventSource türü tarafından paylaşılacak işlevleri tanımlamak yararlı olabilir (aşağıdaki yüksek hacimli olaylar için performansı iyileştirme).

Soyut temel sınıflar anahtar sözcük, görev, işlem kodu, kanal veya olay tanımlamadıkları sürece kullanılabilir. Burada, UtilBaseEventSource sınıfının, aynı bileşendeki birden çok türetilmiş EventSource için gereken iyileştirilmiş bir WriteEvent aşırı yüklemesini tanımladığı bir örnek verilmiştir. Bu türetilmiş türlerden biri aşağıda OptimizedEventSource olarak gösterilmiştir.

public abstract class UtilBaseEventSource : EventSource
{
    protected UtilBaseEventSource()
        : base()
    { }
    protected UtilBaseEventSource(bool throwOnEventWriteErrors)
        : base(throwOnEventWriteErrors)
    { }

    protected unsafe void WriteEvent(int eventId, int arg1, short arg2, long arg3)
    {
        if (IsEnabled())
        {
            EventSource.EventData* descrs = stackalloc EventSource.EventData[2];
            descrs[0].DataPointer = (IntPtr)(&arg1);
            descrs[0].Size = 4;
            descrs[1].DataPointer = (IntPtr)(&arg2);
            descrs[1].Size = 2;
            descrs[2].DataPointer = (IntPtr)(&arg3);
            descrs[2].Size = 8;
            WriteEventCore(eventId, 3, descrs);
        }
    }
}

[EventSource(Name = "OptimizedEventSource")]
public sealed class OptimizedEventSource : UtilBaseEventSource
{
    public static OptimizedEventSource Log { get; } = new OptimizedEventSource();

    [Event(1, Keywords = Keywords.Kwd1, Level = EventLevel.Informational,
           Message = "LogElements called {0}/{1}/{2}.")]
    public void LogElements(int n, short sh, long l)
    {
        WriteEvent(1, n, sh, l); // Calls UtilBaseEventSource.WriteEvent
    }

    #region Keywords / Tasks /Opcodes / Channels
    public static class Keywords
    {
        public const EventKeywords Kwd1 = (EventKeywords)1;
    }
    #endregion
}

Yüksek hacimli olaylar için performansı iyileştirme

EventSource sınıfı, WriteEvent için, değişken sayıda bağımsız değişkeni olanların da dahil olduğu bir dizi aşırı yükleme içerir. Diğer aşırı yüklemelerin hiçbiri eşleşmediğinde params yöntemi çağrılır. Ne yazık ki, parametre aşırı yüklemesi nispeten maliyetlidir. Özellikle o:

  1. Değişken argümanları tutmak için bir dizi tahsis eder.
  2. Her parametreyi bir nesneye dönüştürür ve bu da değer türleri için ayırmalara neden olur.
  3. Bu nesneleri diziye atar.
  4. İşlevi çağırır.
  5. Nasıl serileştirileceğini belirlemek için her dizi öğesinin türünü belirler.

Bu muhtemelen özelleştirilmiş türlerden 10-20 kat daha pahalıdır. Bu, düşük hacimli olaylar için çok önemli değildir, ancak yüksek hacimli olaylar için önemli olabilir. Parametre aşırı yüklemesinin kullanılmadığını garantilemek için iki önemli durum vardır:

  1. Numaralandırılmış türlerin, hızlı aşırı yüklemelerden biriyle eşleşmesi için 'int' türüne dönüştürüldüğünden emin olun.
  2. Yüksek hacimli yükler için yeni hızlı WriteEvent aşırı yüklemeleri oluşturun.

Aşağıda dört tamsayı bağımsız değişkeni alan bir WriteEvent aşırı yüklemesi ekleme örneği verilmiştir

[NonEvent]
public unsafe void WriteEvent(int eventId, int arg1, int arg2,
                              int arg3, int arg4)
{
    EventData* descrs = stackalloc EventProvider.EventData[4];

    descrs[0].DataPointer = (IntPtr)(&arg1);
    descrs[0].Size = 4;
    descrs[1].DataPointer = (IntPtr)(&arg2);
    descrs[1].Size = 4;
    descrs[2].DataPointer = (IntPtr)(&arg3);
    descrs[2].Size = 4;
    descrs[3].DataPointer = (IntPtr)(&arg4);
    descrs[3].Size = 4;

    WriteEventCore(eventId, 4, (IntPtr)descrs);
}