Module: Winlog
- Defined in:
- lib/winlog.rb,
lib/winlog/version.rb,
ext/winlog/winlog.cpp
Overview
winlog โ structured, registration-free ETW TraceLogging telemetry for Ruby: events that cost nothing when nobody is listening.
Register a provider by name (auto name-hashed GUID โ no manifest, no message
DLL, no registry write, no elevation) and emit runtime-dynamic, self-describing
events decodable by WPA, PerfView, and the inbox logman/tracerpt with zero
setup. When no ETW session has enabled the provider, a log call is gated in
C before the fields are even looked at (~one Ruby method call). Emit-only:
events go to ETW sessions, NOT to Event Viewer.
PROV = Winlog.open("MyCompany.MyApp")
PROV.log(:info, "Startup", version: "1.4.2", pid: Process.pid) # => false (no session)
Winlog.open("Tool") { |p| p.log(:info, "Ran", args: ARGV.join(" ")) }
Defined Under Namespace
Classes: Closed, Error, Provider
Constant Summary collapse
- LEVELS =
Symbolic levels -> winmeta values (tracelogging.md ยง5, verified table).
{ critical: 1, # WINEVENT_LEVEL_CRITICAL error: 2, # WINEVENT_LEVEL_ERROR warn: 3, # WINEVENT_LEVEL_WARNING info: 4, # WINEVENT_LEVEL_INFO debug: 5, # WINEVENT_LEVEL_VERBOSE verbose: 5 # alias of :debug }.freeze
- OPCODES =
Symbolic opcodes -> winmeta values. START/STOP bracket activities.
{ info: 0, start: 1, stop: 2 }.freeze
- KEYWORD_RESERVED_MASK =
Keyword bits 48..63 are reserved by Microsoft; winlog REJECTS them.
0xFFFF_0000_0000_0000- VERSION =
"0.1.0"
Class Method Summary collapse
-
.guid_for(name) ⇒ Object
The ETW name-hashed GUID for
nameWITHOUT registering anything. -
.level_for(level) ⇒ Object
Map a level (Symbol in LEVELS or Integer 1..255) to its winmeta Integer.
-
.new_activity_id ⇒ Object
Winlog.new_activity_id -> 36-char lowercase hyphenated GUID String.
-
.opcode_for(opcode) ⇒ Object
Map an opcode (Symbol in OPCODES or Integer 0..255) to its Integer value.
-
.open(name) ⇒ Object
Register a TraceLogging provider under
nameand return a Winlog::Provider.
Class Method Details
.guid_for(name) ⇒ Object
The ETW name-hashed GUID for name WITHOUT registering anything. Pure Ruby
(SHA-1 over the documented signature + UTF-16BE upcased name, .NET byte
order). Same name rules as Winlog.open. Case-insensitive.
Winlog.guid_for("MyCompany.MyComponent")
# => "ce5fa4ea-ab00-5402-8b76-9f76ac858fb5"
81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/winlog.rb', line 81 def guid_for(name) name = validate_name!(name) bytes = Digest::SHA1.digest( GUID_SIGNATURE.pack("C*") + name.upcase.encode("UTF-16BE").b ).bytes[0, 16] bytes[7] = (bytes[7] & 0x0F) | 0x50 [bytes[0, 4].reverse, bytes[4, 2].reverse, bytes[6, 2].reverse, bytes[8, 2], bytes[10, 6]] .map { |part| part.map { |b| format("%02x", b) }.join } .join("-") end |
.level_for(level) ⇒ Object
Map a level (Symbol in LEVELS or Integer 1..255) to its winmeta Integer. Raises ArgumentError on an unknown Symbol or out-of-range Integer.
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/winlog.rb', line 95 def level_for(level) case level when Symbol LEVELS.fetch(level) do raise ArgumentError, "unknown level #{level.inspect} " \ "(known: #{LEVELS.keys.inspect})" end when Integer unless level.between?(1, 255) raise ArgumentError, "level must be 1..255, got #{level.inspect}" end level else raise ArgumentError, "level must be a Symbol or Integer, got #{level.inspect}" end end |
.new_activity_id ⇒ Object
Winlog.new_activity_id -> 36-char lowercase hyphenated GUID String. Wraps EventActivityIdControl(CREATE_ID), which only GENERATES an id and never reads or writes the calling thread's implicit activity id (E17 fiber-safety). Raises Winlog::Error only if the OS call fails (practically never).
744 745 746 747 748 749 750 751 752 753 754 755 756 757 |
# File 'ext/winlog/winlog.cpp', line 744
static VALUE
winlog_new_activity_id(VALUE self)
{
GUID g;
ULONG status;
memset(&g, 0, sizeof g);
status = EventActivityIdControl(EVENT_ACTIVITY_CTRL_CREATE_ID, &g);
if (status != ERROR_SUCCESS)
rb_raise(eError,
"winlog: EventActivityIdControl(CREATE_ID) failed (error %lu)",
(unsigned long)status);
return guid_to_rb(g);
}
|
.opcode_for(opcode) ⇒ Object
Map an opcode (Symbol in OPCODES or Integer 0..255) to its Integer value.
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/winlog.rb', line 114 def opcode_for(opcode) case opcode when Symbol OPCODES.fetch(opcode) do raise ArgumentError, "unknown opcode #{opcode.inspect} " \ "(known: #{OPCODES.keys.inspect})" end when Integer unless opcode.between?(0, 255) raise ArgumentError, "opcode must be 0..255, got #{opcode.inspect}" end opcode else raise ArgumentError, "opcode must be a Symbol or Integer, got #{opcode.inspect}" end end |
.open(name) ⇒ Object
Register a TraceLogging provider under name and return a Winlog::Provider.
Block form yields the provider and ensure-closes it, returning the block
value. Registration is system-wide registration-FREE (no manifest, registry,
or elevation). On a rare EventRegister failure NO exception is raised โ the
provider is a benign no-op; check #registered? (MS guidance).
Winlog.open("X") # => #<Winlog::Provider X {guid}>
Winlog.open("X") { |p| p.log(...) } # => block value; provider closed after
64 65 66 67 68 69 70 71 72 73 |
# File 'lib/winlog.rb', line 64 def open(name) prov = Provider.new(name) return prov unless block_given? begin yield prov ensure prov.close end end |