winlog 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9a6df367ea2d0404effb7f44cb924e4f6fe67dbd5371b6038e056423054d3507
4
- data.tar.gz: 74481ee0141c167adebff54a3e9ceaf2b17122f89899aa681b06214fc486596f
3
+ metadata.gz: 38f927d278f17a348344c5c3f1770549da3312b6c77f6a29bacccbd0d9f9d87d
4
+ data.tar.gz: 043653febe12b66d749c4d51515ee6635a539f42c403469803b177b2da24dc16
5
5
  SHA512:
6
- metadata.gz: 3f48d2c5ae916db9f2fcdf76072464b069d5cc25983c534515fb4455b14f9de3a777dd6de61afe425ee77fb9095d7c8bec8289010d8a9dbbb8b645bef7b15e7d
7
- data.tar.gz: b31ae1e789399d0781e44160c8638b469ad4674e598229fa2823993e68acc6b8390cdb0434fd7656fd5b906cc63deb38677c3c9b1c27cbce4de60d6e17ec87b9
6
+ metadata.gz: 0430a1d51e5832e248a8627485fdd1990d8c3e42149cb7f037c7e6c15d162250fdfbfacbe22c012f3d955c8ac80af01cc446babb3035347402e4c3f48d6db6df
7
+ data.tar.gz: 46eef6776b01377cdb5d451e74a42267037b5bda130d73a2522b307df5e14844473a6786b46f38a4958d30d0cb5bc898bbf8bfb72e54efb8b1a83b37c298fd4a
data/README.md CHANGED
@@ -1,243 +1,11 @@
1
- # winlog
2
-
3
- **Structured, registration-free ETW telemetry for Ruby — TraceLogging events that cost nothing when nobody is listening.**
4
-
5
- `winlog` registers a [TraceLogging](https://learn.microsoft.com/windows/win32/tracelogging/trace-logging-portal)
6
- provider by name (auto name-hashed GUID — **no manifest, no message DLL, no
7
- registry write, no elevation**) and emits runtime-dynamic, self-describing
8
- events: an event name and typed fields decided at call time, decodable by WPA,
9
- PerfView, and the inbox `logman`/`tracerpt` with zero setup.
10
-
11
- The pain it removes: you cannot leave `puts` debugging or verbose file logging on
12
- in production, and a log file is an unstructured island disconnected from the
13
- rest of the system's diagnostics. ETW is the structured, OS-native tracing
14
- pipeline that Windows itself and the .NET runtime emit into — but reaching it
15
- from a scripting language has historically meant authoring a manifest, compiling
16
- a message DLL, and registering it with admin rights. TraceLogging is the
17
- manifest-free encoding that fixes this: every event carries its own schema, so it
18
- decodes **without any registration**. `winlog` is the thin Ruby binding to it.
19
-
20
- - **No setup, anywhere.** `gem install winlog`, `Winlog.open`, `log`. There is no
21
- registration step the gem performs on the system, and no other Ruby gem emits
22
- native ETW. `Logger` writes files; this feeds WPA/PerfView timelines right next
23
- to kernel and .NET events.
24
- - **A disabled `log` costs about one Ruby method call.** When no ETW session has
25
- enabled the provider, the call is gated in native code *before the fields are
26
- even looked at* — no allocation, no transcoding, no iteration. Leave it in
27
- production; that is the entire point of ETW.
28
- - **Hard to misuse.** Reserved keyword bits, level 0, malformed activity GUIDs,
29
- NUL bytes in names, and unknown level symbols all raise loudly. Delivery
30
- problems never raise — ETW is lossy by design, so `log` returns a boolean.
31
-
32
- | What | API |
33
- |---|---|
34
- | Register a provider | `Winlog.open("MyCompany.MyApp")` (block form auto-closes) |
35
- | Emit an event | `provider.log(:info, "Event", field: value, ...)` |
36
- | Cheap "is anyone listening?" | `provider.enabled?(level: :debug, keyword: 0x1)` |
37
- | Name-hashed GUID (no register) | `Winlog.guid_for("MyCompany.MyApp")` |
38
- | Fresh correlation id | `Winlog.new_activity_id` |
39
- | Stop / free | `provider.close` |
40
-
41
- ## Requirements
42
-
43
- - **Windows 10 or later** with a native **MSVC (mswin)** Ruby. Not supported on
44
- MinGW/UCRT Ruby (the extension is built with `cl.exe`).
45
- - Visual Studio 2017+ / Build Tools with the **Desktop development with C++**
46
- workload to build from source. The [`vcvars`](https://github.com/main-path/vcvars)
47
- dev dependency loads the MSVC environment for `rake compile` automatically — no
48
- Developer Command Prompt needed; point build failures at `vcvars doctor`.
49
- - **x64.** arm64-mswin is expected to work but is untested and unsupported until
50
- an arm64-mswin Ruby distribution exists (the code is arch-neutral).
51
-
52
- ## Install
53
-
54
- ```sh
55
- gem install winlog
56
- ```
57
-
58
- ## Quick start
59
-
60
- ```ruby
61
- require "winlog"
62
-
63
- # EXE-lifetime provider: open once at startup, never close (the kernel cleans
64
- # up registration at process exit; the GC free hook is a safety net).
65
- PROV = Winlog.open("MyCompany.MyApp")
66
- PROV.guid # => "ce5fa4ea-..."; hand to logman as "{...}"
67
-
68
- PROV.log(:info, "Startup", version: "1.4.2", pid: Process.pid) # => false (no session)
69
-
70
- # Skip building expensive arguments when nobody is listening:
71
- if PROV.enabled?(level: :debug)
72
- PROV.log(:debug, "CacheDump", entries: cache.size, hot: cache.hot_keys.join(","))
73
- end
74
-
75
- # Activity correlation (fiber-safe: explicit IDs, never the thread-ambient one):
76
- aid = Winlog.new_activity_id
77
- PROV.log(:info, "JobStart", opcode: :start, activity: aid, job: "reindex")
78
- PROV.log(:info, "JobStop", opcode: :stop, activity: aid, ok: true)
79
-
80
- # Misuse raises loudly:
81
- PROV.log(:fatal, "X") # ArgumentError: unknown level :fatal
82
- PROV.log(:info, "X", keyword: 1 << 50) # ArgumentError: reserved keyword bits
83
- Winlog.open("päivä") # ArgumentError: name must be printable ASCII
84
-
85
- # Scoped provider for a short-lived tool:
86
- Winlog.open("MyCompany.Tool") { |p| p.log(:info, "Ran", args: ARGV.join(" ")) }
87
- ```
88
-
89
- `log` returns `true` only when a session had the provider enabled at that
90
- level+keyword **and** the write succeeded; otherwise `false` (no listener, the
91
- provider never registered, or ETW dropped the event). It never raises on
92
- delivery.
93
-
94
- ## Seeing your events
95
-
96
- Collecting a trace requires an **elevated prompt** (or membership in the
97
- *Performance Log Users* group). This is inherent to ETW for *every* provider on
98
- Windows, including Microsoft's own — it is not a `winlog` property and concerns
99
- *collection*, never *emit*. Ranked, copy-pasteable:
100
-
101
- **1. Inbox only — `logman` + `tracerpt` (nothing to install).** `logman` does
102
- not name-hash, so give it `provider.guid`:
103
-
104
- ```sh
105
- logman start rb -p "{ce5fa4ea-ab00-5402-8b76-9f76ac858fb5}" 0xffffffffffffffff 5 -o rb.etl -ets
106
- ruby app.rb
107
- logman stop rb -ets
108
- tracerpt rb.etl -o rb.xml # TDH auto-decodes TraceLogging on Win10+
109
- ```
110
-
111
- **2. WPR (inbox) + WPA (free).** A `.wprp` profile can name the provider with the
112
- star (`*`) syntax, which name-hashes for you:
113
-
114
- ```xml
115
- <EventProvider Id="MyApp" Name="*MyCompany.MyApp" />
116
- ```
117
-
118
- `wpr -start profile.wprp -filemode` → `wpr -stop out.etl` → open in WPA → **System
119
- Activity ▸ Generic Events**.
120
-
121
- **3. PerfView** (single-exe download): `PerfView /onlyProviders=*MyCompany.MyApp collect`.
122
- The `*` converts the name to its GUID the EventSource way.
123
-
124
- > **Events do NOT appear in Event Viewer / `wevtutil`.** That requires registering
125
- > the provider with an Event Log channel (a manifest + `wevtutil im`, i.e.
126
- > registration and admin) — exactly what `winlog` deliberately does not do.
127
- > TraceLogging events go to **ETW sessions**, decoded by the tools above.
128
-
129
- ## Levels, keywords, opcodes
130
-
131
- ```ruby
132
- Winlog::LEVELS # critical:1 error:2 warn:3 info:4 debug:5 (verbose: alias of debug)
133
- Winlog::OPCODES # info:0 start:1 stop:2
134
- ```
135
-
136
- - **Level** is a Symbol from `LEVELS` or an Integer `1..255` (1..5 are the
137
- standard winmeta levels). Level **0 is rejected** — always assign a meaningful
138
- non-zero level.
139
- - **Keyword** is a 64-bit bitmask; bits **0..47 are yours**, bits **48..63 are
140
- reserved by Microsoft** (`Winlog::KEYWORD_RESERVED_MASK`) and setting any of
141
- them raises `ArgumentError`. The default is `0`, which **bypasses keyword
142
- filtering** (ETW semantics) — fine for getting started, but assign meaningful
143
- keywords so sessions can filter.
144
- - **Opcode** `:start`/`:stop` bracket an activity for decoders.
145
-
146
- ## Activities and fibers
147
-
148
- Correlate related events by passing the *same* explicit activity id, generated
149
- with `Winlog.new_activity_id`:
150
-
151
- ```ruby
152
- aid = Winlog.new_activity_id
153
- prov.log(:info, "RequestStart", opcode: :start, activity: aid, related: parent_aid)
154
- # ... work ...
155
- prov.log(:info, "RequestStop", opcode: :stop, activity: aid, status: 200)
156
- ```
157
-
158
- `winlog` **never uses or mutates ETW's thread-ambient activity id.** Under a
159
- fiber scheduler such as [`winloop`](https://github.com/main-path/winloop), one
160
- thread hosts many fibers, and the thread-local id would smear one fiber's
161
- activity across all of them. Correlation is therefore always per-call and
162
- explicit, which is fiber-correct by construction. If you want an ambient pattern,
163
- store the id in `Thread.current[:winlog_activity]` (fiber-local in Ruby) and pass
164
- it yourself.
165
-
166
- `winlog` never blocks, so it never offloads to a worker thread and integrates
167
- with a scheduler trivially: `log`/`enabled?`/`open`/`close` run inline, occupying
168
- the loop thread for at most one ~1–3 µs `EventWriteTransfer` per enabled event.
169
-
170
- ## Costs and validation
171
-
172
- | Operation | Cost (spike-measured, x64) |
173
- |---|---|
174
- | `enabled?` native check (`IsEnabled`) | ~0.4 ns |
175
- | Disabled `log` (gated, fields untouched) | ~one Ruby method call (~0.1 µs) |
176
- | Enabled `EventWriteTransfer` | ~1–3 µs/event (expected; measure under a live session) |
177
-
178
- **The deliberate validation asymmetry (E2).** To keep the disabled path free, a
179
- `log` call validates its *control* arguments on every call — level, event-name
180
- type/content/NUL, `keyword:`/`opcode:`/`activity:`/`related:` — but does **not**
181
- look at field *values* until the event is actually enabled. Consequently a bad
182
- field value (e.g. `nil`, an `Array`) raises `TypeError` **only when a session is
183
- attached**. Use the `enabled?` guard idiom above if you need expensive arguments
184
- built only when watched, and rely on the control-arg checks to keep the common
185
- bugs deterministic regardless.
186
-
187
- Field-value → ETW type mapping:
188
-
189
- | Ruby value | ETW type | notes |
190
- |---|---|---|
191
- | `String` (text encoding) | UTF-8 string | transcoded; embedded NUL or invalid bytes → `ArgumentError` |
192
- | `String` (`Encoding::BINARY`) | binary | UINT16-counted; > 65535 bytes → `ArgumentError` |
193
- | `Integer` | int64 | outside INT64 → `RangeError` |
194
- | `Float` | double | |
195
- | `true` / `false` | bool32 | |
196
- | anything else (incl. `nil`) | — | `TypeError` |
197
-
198
- ## Errors
199
-
200
- ```
201
- Winlog::Error < StandardError # base for what winlog raises itself
202
- └── Winlog::Closed # any op (except close/closed?/inspect) on a closed provider
203
- ```
204
-
205
- Plain argument misuse raises Ruby's own `ArgumentError` / `TypeError` /
206
- `RangeError`. There is deliberately **no `OSError`**: registration failure is a
207
- queryable status (`registered?` is `false`, `registration_result` is the
208
- HRESULT), and write failure is a `false` return — ETW is lossy by design and
209
- Microsoft's guidance is to ignore both. The single OS call with no status channel,
210
- `EventActivityIdControl` inside `Winlog.new_activity_id`, raises `Winlog::Error`
211
- with the Win32 code (it cannot fail in practice).
212
-
213
- ## How it works
214
-
215
- `winlog` is built on Microsoft's **`TraceLoggingDynamic.h`** (vendored into
216
- `ext/winlog/`, © Microsoft, MIT). `TraceLoggingProvider.h` — the usual header —
217
- requires compile-time-constant provider/event/field names, which a runtime Ruby
218
- `log(level, event, **fields)` API cannot supply; `TraceLoggingDynamic.h` is
219
- Microsoft's supported answer for runtime-dynamic events, and (being C++:
220
- templates + `std::vector`) makes `winlog` the suite's first C++ extension.
221
-
222
- Under the hood: `EventRegister` opens a `REGHANDLE` and `EventSetInformation`
223
- attaches the provider traits; the provider GUID is the standard ETW name-hash of
224
- the name (SHA-1 over a fixed signature + the UTF-16BE upper-invariant name, .NET
225
- byte order — the same GUID EventSource, WPR `*name`, PerfView, and
226
- `Winlog.guid_for` compute). Each `log` builds one self-describing event (metadata
227
- + packed field data) and writes it with `EventWriteTransfer`. `enabled?` reads
228
- in-process state maintained by ETW's enable callback — no system call.
229
-
230
- Honest limitations:
231
-
232
- - **Lossy transport.** Even a `true` return does not guarantee a session retained
233
- the event (buffers can be full); a `false` may mean disabled, unregistered, or
234
- dropped.
235
- - **64 KB event ceiling** (ETW), **65535-byte** binary fields (UINT16-counted),
236
- and a ~32 KB metadata cap. Oversized events return `false`, never raise.
237
- - **Emit-only.** No session control, no consumption/decoding, no Event Log
238
- delivery. The five field types above are the whole v1 surface.
239
-
240
- ## License
241
-
242
- [MIT](LICENSE.txt). The vendored `ext/winlog/TraceLoggingDynamic.h` is
243
- © Microsoft Corporation, also MIT.
1
+ # winlog (discontinued)
2
+
3
+ **winlog is no longer maintained.** This final release (0.1.1) contains no
4
+ code: installing it compiles nothing, and `require "winlog"` raises a
5
+ `LoadError` explaining the discontinuation.
6
+
7
+ Use opentelemetry-ruby, or win32-eventlog for the Windows Event Log instead.
8
+
9
+ Earlier releases remain on RubyGems only because RubyGems does not allow
10
+ versions older than 30 days to be yanked. They are unmaintained and not
11
+ recommended.
data/lib/winlog.rb CHANGED
@@ -1,216 +1,4 @@
1
- # frozen_string_literal: true
2
-
3
- require "winlog/version"
4
- require "winlog/winlog" # native extension: Winlog::Provider + Error/Closed + new_activity_id
5
- require "digest"
6
-
7
- # winlog — structured, registration-free ETW TraceLogging telemetry for Ruby:
8
- # events that cost nothing when nobody is listening.
9
- #
10
- # Register a provider by name (auto name-hashed GUID — no manifest, no message
11
- # DLL, no registry write, no elevation) and emit runtime-dynamic, self-describing
12
- # events decodable by WPA, PerfView, and the inbox logman/tracerpt with zero
13
- # setup. When no ETW session has enabled the provider, a `log` call is gated in
14
- # C before the fields are even looked at (~one Ruby method call). Emit-only:
15
- # events go to ETW sessions, NOT to Event Viewer.
16
- #
17
- # PROV = Winlog.open("MyCompany.MyApp")
18
- # PROV.log(:info, "Startup", version: "1.4.2", pid: Process.pid) # => false (no session)
19
- # Winlog.open("Tool") { |p| p.log(:info, "Ran", args: ARGV.join(" ")) }
20
- module Winlog
21
- # Symbolic levels -> winmeta values (tracelogging.md §5, verified table).
22
- LEVELS = {
23
- critical: 1, # WINEVENT_LEVEL_CRITICAL
24
- error: 2, # WINEVENT_LEVEL_ERROR
25
- warn: 3, # WINEVENT_LEVEL_WARNING
26
- info: 4, # WINEVENT_LEVEL_INFO
27
- debug: 5, # WINEVENT_LEVEL_VERBOSE
28
- verbose: 5 # alias of :debug
29
- }.freeze
30
-
31
- # Symbolic opcodes -> winmeta values. START/STOP bracket activities.
32
- OPCODES = { info: 0, start: 1, stop: 2 }.freeze
33
-
34
- # Keyword bits 48..63 are reserved by Microsoft; winlog REJECTS them.
35
- KEYWORD_RESERVED_MASK = 0xFFFF_0000_0000_0000
36
-
37
- # 16 signature bytes prepended to the name before SHA-1, per the documented
38
- # ETW name-hash algorithm (tracelogging.md §4).
39
- GUID_SIGNATURE = [0x48, 0x2C, 0x2D, 0xB2, 0xC3, 0x90, 0x47, 0xC8,
40
- 0x87, 0xF8, 0x1A, 0x15, 0xBF, 0xC1, 0x30, 0xFB].freeze
41
- private_constant :GUID_SIGNATURE
42
-
43
- # Printable ASCII (bytes 0x21..0x7E): no spaces, no control chars, no NUL.
44
- NAME_PATTERN = /\A[\x21-\x7E]{1,255}\z/
45
- private_constant :NAME_PATTERN
46
-
47
- # Base class for everything winlog raises itself. (Error and Closed are
48
- # actually defined in C so they exist before this file's reopen; documented
49
- # here for the reader.)
50
- #
51
- # class Winlog::Error < StandardError; end
52
- # class Winlog::Closed < Winlog::Error; end
53
-
54
- module_function
55
-
56
- # Register a TraceLogging provider under +name+ and return a Winlog::Provider.
57
- # Block form yields the provider and ensure-closes it, returning the block
58
- # value. Registration is system-wide registration-FREE (no manifest, registry,
59
- # or elevation). On a rare EventRegister failure NO exception is raised — the
60
- # provider is a benign no-op; check #registered? (MS guidance).
61
- #
62
- # Winlog.open("X") # => #<Winlog::Provider X {guid}>
63
- # Winlog.open("X") { |p| p.log(...) } # => block value; provider closed after
64
- def open(name)
65
- prov = Provider.new(name)
66
- return prov unless block_given?
67
-
68
- begin
69
- yield prov
70
- ensure
71
- prov.close
72
- end
73
- end
74
-
75
- # The ETW name-hashed GUID for +name+ WITHOUT registering anything. Pure Ruby
76
- # (SHA-1 over the documented signature + UTF-16BE upcased name, .NET byte
77
- # order). Same +name+ rules as Winlog.open. Case-insensitive.
78
- #
79
- # Winlog.guid_for("MyCompany.MyComponent")
80
- # # => "ce5fa4ea-ab00-5402-8b76-9f76ac858fb5"
81
- def guid_for(name)
82
- name = validate_name!(name)
83
- bytes = Digest::SHA1.digest(
84
- GUID_SIGNATURE.pack("C*") + name.upcase.encode("UTF-16BE").b
85
- ).bytes[0, 16]
86
- bytes[7] = (bytes[7] & 0x0F) | 0x50
87
- [bytes[0, 4].reverse, bytes[4, 2].reverse, bytes[6, 2].reverse,
88
- bytes[8, 2], bytes[10, 6]]
89
- .map { |part| part.map { |b| format("%02x", b) }.join }
90
- .join("-")
91
- end
92
-
93
- # Map a level (Symbol in LEVELS or Integer 1..255) to its winmeta Integer.
94
- # Raises ArgumentError on an unknown Symbol or out-of-range Integer.
95
- def level_for(level)
96
- case level
97
- when Symbol
98
- LEVELS.fetch(level) do
99
- raise ArgumentError, "unknown level #{level.inspect} " \
100
- "(known: #{LEVELS.keys.inspect})"
101
- end
102
- when Integer
103
- unless level.between?(1, 255)
104
- raise ArgumentError, "level must be 1..255, got #{level.inspect}"
105
- end
106
- level
107
- else
108
- raise ArgumentError,
109
- "level must be a Symbol or Integer, got #{level.inspect}"
110
- end
111
- end
112
-
113
- # Map an opcode (Symbol in OPCODES or Integer 0..255) to its Integer value.
114
- def opcode_for(opcode)
115
- case opcode
116
- when Symbol
117
- OPCODES.fetch(opcode) do
118
- raise ArgumentError, "unknown opcode #{opcode.inspect} " \
119
- "(known: #{OPCODES.keys.inspect})"
120
- end
121
- when Integer
122
- unless opcode.between?(0, 255)
123
- raise ArgumentError, "opcode must be 0..255, got #{opcode.inspect}"
124
- end
125
- opcode
126
- else
127
- raise ArgumentError,
128
- "opcode must be a Symbol or Integer, got #{opcode.inspect}"
129
- end
130
- end
131
-
132
- # Validate a provider name: printable ASCII (0x21..0x7E), 1..255 bytes.
133
- # Returns the name as a String. Raises ArgumentError on anything else.
134
- def validate_name!(name)
135
- str = String(name)
136
- # Compare raw bytes so a broken/foreign encoding can never make the regex
137
- # match raise (it would on an invalid byte sequence); printable ASCII is a
138
- # pure byte predicate. Reuse the same 0x21..0x7E, 1..255 contract as
139
- # NAME_PATTERN, then return a clean UTF-8 String.
140
- bytes = str.bytes
141
- ok = bytes.length.between?(1, 255) && bytes.all? { |b| b >= 0x21 && b <= 0x7E }
142
- unless ok
143
- raise ArgumentError,
144
- "name must be 1..255 printable-ASCII bytes (0x21..0x7E: no spaces, " \
145
- "control chars, or NUL), got #{name.inspect}"
146
- end
147
- str.b.force_encoding(Encoding::UTF_8)
148
- end
149
- private_class_method :validate_name!
150
-
151
- # Native (TypedData) provider class; the C side defines the bridges and the
152
- # read-only methods. The Ruby layer here adds the validated, ergonomic API.
153
- class Provider
154
- # The provider name as given (frozen UTF-8 String). Stored at initialize and
155
- # never read back from provider metadata (tld swaps in empty NullMetadata on
156
- # a failed registration). Correct regardless of registration outcome.
157
- def name
158
- raise Winlog::Closed, "winlog: provider is closed" if closed?
159
-
160
- @name
161
- end
162
-
163
- # Same contract as Winlog.open (no block form here; open delegates to new).
164
- # Raises ArgumentError on a bad name; never raises on EventRegister failure
165
- # (see #registered?).
166
- def initialize(name)
167
- @name = Winlog.send(:validate_name!, name).dup.freeze
168
- _register(@name)
169
- end
170
-
171
- # Emit one TraceLogging event. THE call of the gem.
172
- #
173
- # level : Symbol in LEVELS, or Integer 1..255.
174
- # event : event name (Symbol or String; valid UTF-8, non-empty, no NUL).
175
- # fields : keyword splat of field-name => value pairs, plus the reserved
176
- # control kwargs keyword:/opcode:/activity:/related: (extracted by
177
- # exact Symbol key; String keys with the same text stay fields).
178
- #
179
- # Returns true if enabled and written, false if skipped/dropped. Never
180
- # raises on delivery problems (ETW is lossy by design). Raises Winlog::Closed
181
- # after close. Field-value type errors raise only when the event is enabled
182
- # (the deliberate E2 asymmetry; control args validate on every call).
183
- def log(level, event, **fields)
184
- # Map opcode symbol -> integer here (Ruby owns the OPCODES table) without
185
- # disturbing the String-key escape hatch: only a :opcode Symbol key is a
186
- # control arg, and only when its value is a Symbol does it need mapping.
187
- if fields.key?(:opcode) && fields[:opcode].is_a?(Symbol)
188
- fields = fields.dup
189
- fields[:opcode] = Winlog.opcode_for(fields[:opcode])
190
- end
191
- _log(Winlog.level_for(level), event, fields)
192
- end
193
-
194
- # Is any ETW session listening (with the given level/keyword filter)?
195
- # Reads in-process enable state — no system call. Raises Winlog::Closed
196
- # after close.
197
- #
198
- # level: nil (default; "any level") | Symbol in LEVELS | Integer 1..255
199
- # keyword: Integer 0..0x0000_FFFF_FFFF_FFFF (reserved bits raise)
200
- def enabled?(level: nil, keyword: 0)
201
- lvl = level.nil? ? nil : Winlog.level_for(level)
202
- _enabled(lvl, keyword)
203
- end
204
-
205
- # Never raises, even closed.
206
- def inspect
207
- if closed?
208
- "#<Winlog::Provider (closed)>"
209
- else
210
- "#<Winlog::Provider #{@name} {#{guid}}>"
211
- end
212
- end
213
-
214
- private :_register, :_log, :_write, :_enabled
215
- end
216
- end
1
+ # frozen_string_literal: true
2
+
3
+ raise LoadError, "winlog is discontinued and this release contains no code. Use opentelemetry-ruby, or win32-eventlog for the Windows Event Log instead." \
4
+ " Earlier winlog releases are unmaintained and not recommended."
metadata CHANGED
@@ -1,109 +1,30 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: winlog
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - ned
8
8
  bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
- dependencies:
12
- - !ruby/object:Gem::Dependency
13
- name: rake
14
- requirement: !ruby/object:Gem::Requirement
15
- requirements:
16
- - - "~>"
17
- - !ruby/object:Gem::Version
18
- version: '13.0'
19
- type: :development
20
- prerelease: false
21
- version_requirements: !ruby/object:Gem::Requirement
22
- requirements:
23
- - - "~>"
24
- - !ruby/object:Gem::Version
25
- version: '13.0'
26
- - !ruby/object:Gem::Dependency
27
- name: rake-compiler
28
- requirement: !ruby/object:Gem::Requirement
29
- requirements:
30
- - - "~>"
31
- - !ruby/object:Gem::Version
32
- version: '1.2'
33
- type: :development
34
- prerelease: false
35
- version_requirements: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - "~>"
38
- - !ruby/object:Gem::Version
39
- version: '1.2'
40
- - !ruby/object:Gem::Dependency
41
- name: minitest
42
- requirement: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - "~>"
45
- - !ruby/object:Gem::Version
46
- version: '5.0'
47
- type: :development
48
- prerelease: false
49
- version_requirements: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - "~>"
52
- - !ruby/object:Gem::Version
53
- version: '5.0'
54
- - !ruby/object:Gem::Dependency
55
- name: vcvars
56
- requirement: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - "~>"
59
- - !ruby/object:Gem::Version
60
- version: '0.1'
61
- - - ">="
62
- - !ruby/object:Gem::Version
63
- version: 0.1.1
64
- type: :development
65
- prerelease: false
66
- version_requirements: !ruby/object:Gem::Requirement
67
- requirements:
68
- - - "~>"
69
- - !ruby/object:Gem::Version
70
- version: '0.1'
71
- - - ">="
72
- - !ruby/object:Gem::Version
73
- version: 0.1.1
74
- description: |
75
- winlog emits Windows ETW TraceLogging events from Ruby with no manifest, no
76
- message DLL, no registry writes, and no elevation: register a provider by
77
- name (standard ETW name-hashed GUID), then log runtime-dynamic events with
78
- typed fields (UTF-8 text, binary, int64, double, boolean) plus levels,
79
- keywords, opcodes, and explicit activity IDs for correlation. Events are
80
- self-describing and decode in WPA, PerfView, and the inbox logman/tracerpt
81
- with zero setup; when no session is listening, a log call is gated in native
82
- code before field processing and costs about one Ruby method call. Built on
83
- Microsoft's MIT-licensed TraceLoggingDynamic.h (vendored). Emit-only: events
84
- go to ETW sessions, not the Windows Event Log. Windows MSVC (mswin) Ruby only.
11
+ dependencies: []
12
+ description: winlog is discontinued. This final release contains no code and exists
13
+ only so that installing winlog explains the discontinuation instead of building
14
+ an unmaintained native extension.
85
15
  executables: []
86
- extensions:
87
- - ext/winlog/extconf.rb
16
+ extensions: []
88
17
  extra_rdoc_files: []
89
18
  files:
90
- - CHANGELOG.md
91
19
  - LICENSE.txt
92
20
  - README.md
93
- - ext/winlog/TraceLoggingDynamic.h
94
- - ext/winlog/extconf.rb
95
- - ext/winlog/winlog.cpp
96
21
  - lib/winlog.rb
97
- - lib/winlog/version.rb
98
- homepage: https://github.com/main-path/winlog
99
22
  licenses:
100
23
  - MIT
101
24
  metadata:
102
- homepage_uri: https://github.com/main-path/winlog
103
- source_code_uri: https://github.com/main-path/winlog
104
- changelog_uri: https://github.com/main-path/winlog/blob/main/CHANGELOG.md
105
- bug_tracker_uri: https://github.com/main-path/winlog/issues
106
25
  rubygems_mfa_required: 'true'
26
+ post_install_message: winlog is discontinued and this release contains no code. Use
27
+ opentelemetry-ruby, or win32-eventlog for the Windows Event Log instead.
107
28
  rdoc_options: []
108
29
  require_paths:
109
30
  - lib
@@ -111,7 +32,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
111
32
  requirements:
112
33
  - - ">="
113
34
  - !ruby/object:Gem::Version
114
- version: '3.0'
35
+ version: '2.0'
115
36
  required_rubygems_version: !ruby/object:Gem::Requirement
116
37
  requirements:
117
38
  - - ">="
@@ -120,6 +41,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
120
41
  requirements: []
121
42
  rubygems_version: 3.6.9
122
43
  specification_version: 4
123
- summary: Structured, registration-free ETW TraceLogging telemetry for Ruby — events
124
- that cost nothing when nobody is listening.
44
+ summary: Discontinued. Use opentelemetry-ruby, or win32-eventlog for the Windows Event
45
+ Log instead.
125
46
  test_files: []
data/CHANGELOG.md DELETED
@@ -1,31 +0,0 @@
1
- # Changelog
2
-
3
- ## [0.1.0] - 2026-06-27
4
-
5
- Initial release.
6
-
7
- - `Winlog.open(name)` — register a TraceLogging provider by name (standard ETW
8
- name-hashed GUID; no manifest, message DLL, registry write, or elevation),
9
- returning a `Winlog::Provider`. Block form yields the provider and
10
- ensure-closes it.
11
- - `Winlog::Provider#log(level, event, **fields)` — emit one runtime-dynamic,
12
- self-describing event with typed fields (UTF-8 text, binary, int64, double,
13
- boolean) plus `keyword:`, `opcode:`, and explicit `activity:`/`related:` GUIDs
14
- for correlation. Gated in native code before any field is read when no session
15
- is listening (~one Ruby method call); never raises on delivery problems.
16
- - `Winlog::Provider#enabled?(level:, keyword:)` — in-process check (no system
17
- call) for skipping expensive argument building.
18
- - `Winlog::Provider#name` / `#guid` / `#registered?` / `#registration_result` /
19
- `#close` / `#closed?` / `#inspect`.
20
- - `Winlog.guid_for(name)` — the pure-Ruby ETW name-hash, for handing the GUID to
21
- logman (which does not name-hash).
22
- - `Winlog.new_activity_id` — a fresh 128-bit activity id
23
- (`EventActivityIdControl(CREATE_ID)`), fiber-safe (never touches the
24
- thread-ambient activity id).
25
- - Constants `Winlog::LEVELS`, `Winlog::OPCODES`, `Winlog::KEYWORD_RESERVED_MASK`.
26
- - Errors `Winlog::Error` and `Winlog::Closed`; plain argument misuse raises
27
- Ruby's own `ArgumentError` / `TypeError` / `RangeError`.
28
-
29
- Built on Microsoft's MIT-licensed `TraceLoggingDynamic.h` (vendored into
30
- `ext/winlog/`); winlog implements no ETW encoding of its own. Emit-only: events
31
- go to ETW sessions, not the Windows Event Log. Windows MSVC (mswin) Ruby only.