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 +4 -4
- data/README.md +11 -243
- data/lib/winlog.rb +4 -216
- metadata +11 -90
- data/CHANGELOG.md +0 -31
- data/ext/winlog/TraceLoggingDynamic.h +0 -3482
- data/ext/winlog/extconf.rb +0 -30
- data/ext/winlog/winlog.cpp +0 -788
- data/lib/winlog/version.rb +0 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 38f927d278f17a348344c5c3f1770549da3312b6c77f6a29bacccbd0d9f9d87d
|
|
4
|
+
data.tar.gz: 043653febe12b66d749c4d51515ee6635a539f42c403469803b177b2da24dc16
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0430a1d51e5832e248a8627485fdd1990d8c3e42149cb7f037c7e6c15d162250fdfbfacbe22c012f3d955c8ac80af01cc446babb3035347402e4c3f48d6db6df
|
|
7
|
+
data.tar.gz: 46eef6776b01377cdb5d451e74a42267037b5bda130d73a2522b307df5e14844473a6786b46f38a4958d30d0cb5bc898bbf8bfb72e54efb8b1a83b37c298fd4a
|
data/README.md
CHANGED
|
@@ -1,243 +1,11 @@
|
|
|
1
|
-
# winlog
|
|
2
|
-
|
|
3
|
-
**
|
|
4
|
-
|
|
5
|
-
`
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
4
|
-
|
|
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.
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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: '
|
|
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:
|
|
124
|
-
|
|
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.
|