Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Artikel ini berlaku untuk: ✔️ .NET Core 3.1 dan versi yang lebih baru ✔️ .NET Framework 4.5 dan versi yang lebih baru
Panduan memulai menunjukkan kepada Anda cara membuat EventSource minimal dan mengumpulkan peristiwa dalam file pelacakan. Tutorial ini membahas secara lebih rinci tentang membuat acara menggunakan System.Diagnostics.Tracing.EventSource.
Sebuah EventSource minimal
[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);
}
Struktur dasar EventSource turunan selalu sama. Khususnya:
- Kelas mewarisi dari System.Diagnostics.Tracing.EventSource
- Untuk setiap jenis peristiwa yang ingin Anda hasilkan, metode perlu didefinisikan. Metode ini harus diberi nama menggunakan nama peristiwa yang sedang dibuat. Jika peristiwa memiliki data tambahan, ini harus diteruskan menggunakan argumen. Argumen peristiwa ini perlu diserialisasikan sehingga hanya jenis tertentu yang diizinkan.
- Setiap metode memiliki isi yang menyebut WriteEvent meneruskannya ID (nilai numerik yang mewakili peristiwa) dan argumen metode peristiwa. ID harus unik dalam EventSource. ID secara eksplisit ditetapkan menggunakan System.Diagnostics.Tracing.EventAttribute
- EventSources dimaksudkan sebagai instance tunggal. Dengan demikian lebih mudah untuk menentukan variabel statis, menurut konvensi yang disebut
Log, yang mewakili singleton ini.
Aturan untuk menentukan metode peristiwa
- Setiap metode non-virtual yang tidak mengembalikan nilai dan didefinisikan dalam kelas EventSource secara default adalah metode pencatatan peristiwa.
- Metode virtual atau non-void-returning hanya disertakan jika ditandai dengan System.Diagnostics.Tracing.EventAttribute
- Untuk menandai metode yang memenuhi syarat sebagai non-pengelogan, Anda harus menghiasnya dengan System.Diagnostics.Tracing.NonEventAttribute
- Metode pengelogan peristiwa memiliki ID peristiwa yang terkait dengannya. Ini dapat dilakukan baik secara eksplisit dengan mendekorasi metode dengan System.Diagnostics.Tracing.EventAttribute atau secara implisit dengan jumlah ordinal metode di kelas . Misalnya menggunakan penomoran implisit metode pertama di kelas memiliki ID 1, yang kedua memiliki ID 2, dan sebagainya.
- Metode pencatatan peristiwa harus memanggil overload WriteEvent, WriteEventCore, WriteEventWithRelatedActivityId, atau WriteEventWithRelatedActivityIdCore.
- ID peristiwa, baik tersirat maupun eksplisit, harus cocok dengan argumen pertama yang diteruskan ke API WriteEvent* yang dipanggilnya.
- Jumlah, jenis, dan urutan argumen yang diteruskan ke metode EventSource harus selaras dengan cara argumen diteruskan ke API WriteEvent*. Untuk WriteEvent, argumen mengikuti ID Peristiwa, untuk WriteEventWithRelatedActivityId, argumen mengikuti relatedActivityId. Untuk metode WriteEvent*Core, argumen harus diserialisasikan secara manual ke dalam parameter
data. - Nama peristiwa tidak boleh berisi karakter
<atau>. Meskipun metode yang ditentukan pengguna juga tidak dapat berisi karakter ini, metodeasyncakan ditulis ulang oleh kompilator untuk memuatnya. Untuk memastikan metode yang dihasilkan ini tidak menjadi peristiwa, tandai semua metode non-peristiwa pada EventSource dengan NonEventAttribute.
Praktik terbaik
- Jenis yang berasal dari EventSource biasanya tidak memiliki jenis perantara dalam hierarki atau antarmuka implementasi. Lihat kustomisasi tingkat lanjut di bawah ini untuk beberapa pengecualian di mana ini mungkin berguna.
- Umumnya nama kelas EventSource adalah nama publik yang buruk untuk EventSource. Nama publik, nama yang akan muncul dalam konfigurasi pengelogan dan penampil log, harus unik secara global. Sebaiknya memberikan nama publik pada EventSource milik Anda menggunakan System.Diagnostics.Tracing.EventSourceAttribute. Nama "Demo" yang digunakan di atas singkat dan tidak mungkin unik sehingga bukan pilihan yang baik untuk penggunaan produksi. Konvensi umum adalah menggunakan nama hierarkis dengan
.atau-sebagai pemisah, seperti "MyCompany-Samples-Demo", atau nama Assembly atau namespace layanan tempat EventSource menyediakan peristiwa. Tidak disarankan untuk menyertakan "EventSource" sebagai bagian dari nama publik. - Tetapkan ID Acara secara eksplisit, dengan demikian perubahan yang tampaknya jinak pada kode di kelas sumber, seperti mengatur ulang kode atau menambahkan metode di tengah, tidak akan mengubah ID Acara yang terkait dengan setiap metode.
- Saat menulis peristiwa yang mewakili awal dan akhir unit kerja, menurut konvensi metode ini dinamai dengan akhiran 'Mulai' dan 'Berhenti'. Misalnya, 'RequestStart' dan 'RequestStop'.
- Jangan tentukan nilai eksplisit untuk properti Guid EventSourceAttribute, kecuali Anda membutuhkannya karena alasan kompatibilitas mundur. Nilai Guid default berasal dari nama sumber, yang memungkinkan alat untuk menerima nama yang lebih dapat dibaca manusia dan memperoleh Guid yang sama.
- Panggil IsEnabled() sebelum melakukan pekerjaan yang memakan banyak sumber daya terkait dengan memicu kejadian, seperti menghitung argumen kejadian yang memakan banyak sumber daya yang tidak akan diperlukan jika kejadian dinonaktifkan.
- Cobalah untuk menjaga agar objek EventSource kembali kompatibel dan membuat versinya dengan tepat. Versi default untuk peristiwa adalah 0. Versi dapat diubah dengan mengatur EventAttribute.Version. Ubah versi peristiwa setiap kali Anda mengubah data yang diserialisasikan dengannya. Selalu tambahkan data berseri baru ke akhir deklarasi peristiwa, yaitu, di akhir daftar parameter metode. Jika ini tidak memungkinkan, buat peristiwa baru dengan ID baru untuk mengganti yang lama.
- Saat mendeklarasikan metode peristiwa, tentukan data payload ukuran tetap sebelum data berukuran bervariasi.
- Jangan gunakan string yang berisi karakter null. Saat menghasilkan manifes untuk ETW, EventSource akan mendeklarasikan semua string dengan tanda null di akhir, meskipun dimungkinkan untuk memiliki karakter null dalam String C#. Jika string berisi karakter null, seluruh string akan ditulis ke payload peristiwa, tetapi pengurai apa pun akan memperlakukan karakter null pertama sebagai akhir string. Jika ada argumen payload setelah string, sisa string akan diurai alih-alih nilai yang dimaksudkan.
Kustomisasi acara standar
Mengatur tingkat verbositas peristiwa
Setiap acara memiliki tingkat verbositas dan pelanggan acara sering mengaktifkan semua acara pada EventSource hingga tingkat verbositas tertentu. Event mendeklarasikan tingkat verbositasnya menggunakan properti Level. Misalnya dalam EventSource di bawah ini, pelanggan yang meminta peristiwa pada tingkat Informasi dan lebih rendah tidak akan mencatat peristiwa dari jenis Verbose DebugMessage.
[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);
}
Jika tingkat verbositas peristiwa tidak ditentukan dalam EventAttribute, maka akan menjadi Informasi secara default.
Praktik terbaik
Gunakan tingkat di bawah tingkat Informasi untuk peringatan atau kesalahan yang relatif jarang terjadi. Ketika ragu, tetap dengan default Informasi dan gunakan Verbose untuk peristiwa yang terjadi lebih sering dari 1000 peristiwa/detik.
Mengatur kata kunci peristiwa
Beberapa sistem pelacakan peristiwa mendukung kata kunci sebagai mekanisme pemfilteran tambahan. Tidak seperti verbositas yang mengategorikan peristiwa berdasarkan tingkat detail, kata kunci dimaksudkan untuk mengategorikan peristiwa berdasarkan kriteria lain seperti area fungsionalitas kode atau yang akan berguna untuk mendiagnosis masalah tertentu. Kata kunci diberi nama bendera bit dan setiap peristiwa dapat memiliki kombinasi kata kunci yang diterapkan padanya. Misalnya EventSource di bawah ini mendefinisikan beberapa peristiwa yang terkait dengan pemrosesan permintaan dan peristiwa lain yang terkait dengan startup. Jika pengembang ingin menganalisis performa startup, mereka mungkin hanya mengaktifkan pengelogan peristiwa yang ditandai dengan kata kunci startup.
[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;
}
}
Kata kunci harus didefinisikan dengan menggunakan kelas berlapis yang disebut Keywords dan setiap kata kunci individu ditentukan oleh anggota yang ditik public const EventKeywords.
Praktik terbaik
Kata kunci lebih penting ketika membedakan antara peristiwa volume tinggi. Ini memungkinkan konsumen acara untuk meningkatkan tingkat verbositas namun tetap dapat mengontrol dampak terhadap performa dan ukuran log dengan hanya mengaktifkan bagian-bagian kecil dari acara tersebut. Peristiwa yang dipicu lebih dari 1.000/detik adalah kandidat yang baik untuk kata kunci unik.
Jenis parameter yang didukung
EventSource mengharuskan semua parameter peristiwa dapat diserialisasikan sehingga hanya menerima serangkaian jenis terbatas. Ini adalah:
- Primitif: bool, byte, sbyte, char, short, ushort, int, uint, long, ulong, float, double, IntPtr, dan UIntPtr, Guid decimal, string, DateTime, DateTimeOffset, TimeSpan
- Enum
- Struktur yang dikaitkan dengan System.Diagnostics.Tracing.EventDataAttribute. Hanya properti instans publik dengan jenis yang dapat diserialisasi yang akan diserialisasi.
- Jenis anonim di mana semua properti publik adalah jenis yang dapat diserialisasikan
- Array jenis yang dapat diserialisasikan
- <T> yang dapat di-null-kan di mana T adalah tipe yang dapat diserialisasikan
- KeyValuePair<T, U> di mana T dan U keduanya adalah tipe yang dapat diserialisasi
- Jenis yang mengimplementasikan IEnumerable<T> untuk tepat satu jenis T dan di mana T adalah jenis yang dapat diserialisasikan
Pemecahan masalah
Kelas EventSource dirancang agar tidak pernah melempar Pengecualian secara default. Ini adalah properti yang berguna, karena pengelogan sering diperlakukan sebagai opsional, dan Anda biasanya tidak ingin kesalahan menulis pesan log menyebabkan aplikasi Anda gagal. Namun, ini membuat menemukan kesalahan di EventSource Anda sulit. Berikut adalah beberapa teknik yang dapat membantu memecahkan masalah:
- Konstruktor EventSource memiliki kelebihan beban yang mengambil EventSourceSettings. Coba aktifkan bendera ThrowOnEventWriteErrors untuk sementara.
- Properti EventSource.ConstructionException menyimpan Pengecualian apa pun yang dihasilkan saat memvalidasi metode pengelogan peristiwa. Ini dapat mengungkapkan berbagai kesalahan penulisan.
- EventSource mencatat kesalahan menggunakan ID peristiwa 0, dan peristiwa kesalahan ini berisi string yang menjelaskan kesalahan.
- Saat melakukan debugging, string kesalahan yang sama juga akan dicatat dengan Debug.WriteLine() dan muncul di jendela output debug.
- EventSource secara internal melempar dan kemudian menangkap pengecualian ketika kesalahan terjadi. Untuk mengamati kapan pengecualian ini terjadi, aktifkan pengecualian kesempatan pertama di debugger, atau gunakan pelacakan peristiwa dengan mengaktifkan peristiwa Pengecualian pada runtime .NET.
Kustomisasi tingkat lanjut
Mengatur OpCodes dan Tugas
ETW memiliki konsep Tasks dan OpCodes, yang merupakan mekanisme lebih lanjut untuk menandai dan memfilter peristiwa. Anda dapat mengaitkan peristiwa dengan tugas dan opcode tertentu menggunakan properti Task dan Opcode. Berikut adalah contohnya:
[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;
}
}
Anda dapat secara implisit membuat objek EventTask dengan mendeklarasikan dua metode peristiwa dengan ID peristiwa berikutnya yang memiliki pola penamaan <EventName>Mulai dan <EventName>Berhenti. Peristiwa ini harus dideklarasikan di samping satu sama lain dalam definisi kelas dan metode <EventName>Start harus menjadi yang pertama.
Deskriptif mandiri (Tracelogging) vs. format acara manifes
Konsep ini hanya penting saat berlangganan EventSource dari ETW. ETW memiliki dua cara berbeda untuk mencatat peristiwa, format manifes dan format yang mendeskripsikan diri sendiri (kadang-kadang disebut sebagai tracelogging). Objek EventSource berbasis manifes menghasilkan dan mencatat dokumen XML yang mewakili peristiwa yang ditentukan pada kelas setelah inisialisasi. Ini mengharuskan EventSource melakukan refleksi atas dirinya sendiri untuk menghasilkan penyedia dan metadata acara. Dalam metadata format yang dapat menjelaskan diri sendiri, untuk setiap peristiwa ditransmisikan sebaris dengan data peristiwa dibandingkan dengan di muka. Pendekatan yang menjelaskan sendiri mendukung metode Write yang lebih fleksibel yang dapat mengirim peristiwa arbitrer tanpa membuat metode pengelogan peristiwa yang telah ditentukan sebelumnya. Ini juga sedikit lebih cepat saat mulai karena menghindari refleksi cepat. Namun, metadata tambahan yang dihasilkan dengan setiap kejadian menambahkan sedikit beban kinerja, yang mungkin tidak diinginkan saat mengirim jumlah kejadian yang besar.
Untuk menggunakan format peristiwa yang menjelaskan dirinya sendiri, buat EventSource Anda dengan menggunakan konstruktor EventSource(String), konstruktor EventSource(String, EventSourceSettings), atau dengan mengatur penanda EtwSelfDescribingEventFormat pada EventSourceSettings.
Jenis EventSource yang mengimplementasikan antarmuka
Jenis EventSource dapat mengimplementasikan antarmuka untuk berintegrasi dengan mulus dalam berbagai sistem pengelogan tingkat lanjut yang menggunakan antarmuka untuk menentukan target pengelogan umum. Berikut adalah contoh kemungkinan penggunaan:
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); }
}
Anda harus menentukan EventAttribute pada metode antarmuka, jika tidak (karena alasan kompatibilitas) metode tidak akan diperlakukan sebagai metode pengelogan. Implementasi metode interface eksplisit tidak diizinkan untuk mencegah konflik penamaan.
Hierarki kelas EventSource
Dalam kebanyakan kasus, Anda akan dapat menulis jenis yang langsung berasal dari kelas EventSource. Akan tetapi, terkadang berguna untuk mendefinisikan fungsionalitas yang akan dibagikan oleh beberapa jenis EventSource turunan, seperti WriteEvent overload yang dikustomisasi (lihat mengoptimalkan performa untuk peristiwa dengan volume tinggi di bawah).
Kelas dasar abstrak dapat digunakan selama tidak menentukan kata kunci, tugas, opcode, saluran, atau peristiwa apa pun. Berikut adalah contoh di mana kelas UtilBaseEventSource mendefinisikan kelebihan beban WriteEvent yang dioptimalkan, ini diperlukan oleh beberapa EventSource turunan dalam komponen yang sama. Salah satu jenis turunan ini diilustrasikan di bawah ini sebagai OptimizedEventSource.
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
}
Mengoptimalkan performa untuk peristiwa volume tinggi
Kelas EventSource memiliki sejumlah kelebihan beban untuk WriteEvent, termasuk satu untuk jumlah variabel argumen. Ketika tidak ada kelebihan beban lain yang cocok, metode param dipanggil. Sayangnya, beban pemakaian parameter relatif mahal. Secara khusus:
- Mengalokasikan array untuk menahan argumen variabel.
- Mengonversi setiap parameter menjadi objek, yang menyebabkan alokasi untuk tipe nilai.
- Menetapkan objek ini ke array.
- Memanggil fungsi .
- Mencari tahu jenis setiap elemen array untuk menentukan cara menserialisasikannya.
Ini mungkin 10 hingga 20 kali lebih mahal dari jenis khusus. Ini tidak masalah banyak untuk kasus volume rendah, tetapi untuk peristiwa volume tinggi itu bisa menjadi penting. Ada dua kasus penting untuk memastikan bahwa kelebihan parameter tidak digunakan:
- Pastikan bahwa jenis enumerasi dilemparkan ke 'int' sehingga cocok dengan salah satu kelebihan beban yang cepat.
- Buat kelebihan beban WriteEvent cepat baru untuk payload volume tinggi.
Berikut adalah contoh untuk menambahkan kelebihan beban WriteEvent yang mengambil empat argumen bilangan bulat
[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);
}