├── .gitignore ├── go.mod ├── eventwatcher.go ├── channels.go ├── enum.go ├── eventwatcher_common.go ├── eventparser.go ├── typedef.go ├── _example ├── unixwatch │ ├── main.go │ └── unixwatch_test.go └── winwatch │ ├── winwatch_test.go │ └── main.go ├── LICENSE ├── memprofile_test.go ├── eventwatcher_unix_test.go ├── channels_windows.go ├── README_ZH.md ├── eventnotifier.go ├── eventwatcher_test.go ├── eventparser_windows.go ├── README.md ├── eventwatcher_windows.go ├── eventwatcher_unix.go ├── event.go └── event_windows.go /.gitignore: -------------------------------------------------------------------------------- 1 | _.* 2 | .DS_Store 3 | cmd.go 4 | .vs_code 5 | go.sum 6 | *.log -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/auuunya/eventwatcher 2 | 3 | go 1.19 4 | 5 | require golang.org/x/sys v0.21.0 6 | 7 | require github.com/fsnotify/fsnotify v1.5.4 // indirect 8 | -------------------------------------------------------------------------------- /eventwatcher.go: -------------------------------------------------------------------------------- 1 | package eventwatcher 2 | 3 | // platform-specific EventWatcher implementations live in 4 | // eventwatcher_windows.go and eventwatcher_unix.go. This file is a 5 | // placeholder to avoid duplicate declarations. 6 | -------------------------------------------------------------------------------- /channels.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package eventwatcher 5 | 6 | // Non-Windows stubs for channel enumeration APIs. 7 | func EvtOpenChannelEnum(session uintptr) (uintptr, error) { return 0, nil } 8 | func EvtClose(handle uintptr) error { return nil } 9 | func EvtNextChannelPath(handle uintptr) ([]string, error) { return nil, nil } 10 | -------------------------------------------------------------------------------- /enum.go: -------------------------------------------------------------------------------- 1 | package eventwatcher 2 | 3 | type SID_NAME_USE uint32 4 | 5 | const ( 6 | // https://learn.microsoft.com/zh-cn/windows/win32/api/winnt/ne-winnt-sid_name_use 7 | SidTypeUser SID_NAME_USE = iota + 1 8 | SidTypeGroup 9 | SidTypeDomain 10 | SidTypeAlias 11 | SidTypeWellKnownGroup 12 | SidTypeDeletedAccount 13 | SidTypeInvalid 14 | SidTypeUnknown 15 | SidTypeComputer 16 | SidTypeLabel 17 | SidTypeLogonSession 18 | ) 19 | -------------------------------------------------------------------------------- /eventwatcher_common.go: -------------------------------------------------------------------------------- 1 | package eventwatcher 2 | 3 | import "context" 4 | 5 | // Common EventWatcher fields shared across platforms. 6 | // Platform-specific files implement the platform behavior (Init, Listen, CloseHandles). 7 | 8 | type EventWatcher struct { 9 | Name string 10 | handle uintptr 11 | offset uint32 12 | eventHandle uintptr 13 | cancelHandle uintptr 14 | ctx context.Context 15 | cancel context.CancelFunc 16 | eventChan chan *EventEntry 17 | stopCh chan struct{} 18 | } -------------------------------------------------------------------------------- /eventparser.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package eventwatcher 5 | 6 | // Non-Windows minimal parser stubs. 7 | // On non-Windows platforms we don't have EventLog structures; these 8 | // functions provide safe fallbacks for compilation and will be 9 | // implemented properly later when adding Unix event support. 10 | 11 | type EventLogRecord struct{} 12 | 13 | func ParseEventLogData(buf []byte) *EventLogRecord { 14 | return &EventLogRecord{} 15 | } 16 | 17 | func ParserEventLogData(buf []byte) (*EventLogRecord, error) { 18 | return &EventLogRecord{}, nil 19 | } 20 | 21 | func FormatContent(buf []byte) string { 22 | return "" 23 | } 24 | 25 | func FormatMessage(errorCode uint32) string { 26 | return "" 27 | } 28 | 29 | func LookupAccountSid(buf []byte, sidlen, sidoffset uint32) (string, string, error) { 30 | return "", "", nil 31 | } -------------------------------------------------------------------------------- /typedef.go: -------------------------------------------------------------------------------- 1 | package eventwatcher 2 | 3 | import ( 4 | "syscall" 5 | ) 6 | 7 | const ( 8 | // Use uintptr for cross-platform handle representation. 9 | InvalidHandle = uintptr(0) 10 | 11 | ERROR_HANDLE_EOF syscall.Errno = 38 12 | ERROR_INSUFFICIENT_BUFFER syscall.Errno = 122 13 | ERROR_NO_MORE_ITEMS syscall.Errno = 259 14 | NO_ERROR = 0 15 | ) 16 | 17 | const ( 18 | EVENTLOG_SUCCESS = 0x0000 19 | EVENTLOG_ERROR_TYPE = 0x0001 20 | EVENTLOG_WARNING_TYPE = 0x0002 21 | EVENTLOG_INFORMATION_TYPE = 0x0004 22 | EVENTLOG_AUDIT_SUCCESS = 0x0008 23 | EVENTLOG_AUDIT_FAILURE = 0x0010 24 | ) 25 | 26 | const ( 27 | // https://learn.microsoft.com/zh-cn/windows/win32/api/winbase/nf-winbase-readeventloga 28 | EVENTLOG_SEEK_READ = 0x0002 29 | EVENTLOG_SEQUENTIAL_READ = 0x0001 30 | 31 | EVENTLOG_FORWARDS_READ = 0x0004 32 | EVENTLOG_BACKWARDS_READ = 0x0008 33 | ) 34 | -------------------------------------------------------------------------------- /_example/unixwatch/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "os/signal" 8 | "syscall" 9 | "time" 10 | 11 | "github.com/auuunya/eventwatcher" 12 | ) 13 | 14 | func main() { 15 | // Usage: pass a file path to watch via environment variable or default to ./example.log 16 | path := os.Getenv("EW_PATH") 17 | if path == "" { 18 | path = "example.log" 19 | } 20 | 21 | ctx := context.Background() 22 | n := eventwatcher.NewEventNotifier(ctx) 23 | defer n.Close() 24 | 25 | if err := n.AddWatcher(path); err != nil { 26 | fmt.Printf("failed to add watcher: %v\n", err) 27 | return 28 | } 29 | 30 | go func() { 31 | for ev := range n.EventLogChannel { 32 | fmt.Printf("event: name=%s len=%d content=%q\n", ev.Name, len(ev.Buffer), string(ev.Buffer)) 33 | } 34 | }() 35 | 36 | // Just run until interrupted 37 | quit := make(chan os.Signal, 1) 38 | signal.Notify(quit, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM) 39 | <-quit 40 | // small grace period to flush 41 | time.Sleep(100 * time.Millisecond) 42 | fmt.Println("exiting") 43 | } -------------------------------------------------------------------------------- /_example/unixwatch/unixwatch_test.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | "os" 9 | "testing" 10 | "time" 11 | 12 | "github.com/auuunya/eventwatcher" 13 | ) 14 | 15 | func TestUnixWatchExample(t *testing.T) { 16 | f, err := os.CreateTemp("", "example_unix_*.log") 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | defer os.Remove(f.Name()) 21 | f.Close() 22 | 23 | ctx := context.Background() 24 | n := eventwatcher.NewEventNotifier(ctx) 25 | defer n.Close() 26 | 27 | if err := n.AddWatcher(f.Name()); err != nil { 28 | t.Fatalf("AddWatcher failed: %v", err) 29 | } 30 | 31 | // Give watcher time to start 32 | time.Sleep(100 * time.Millisecond) 33 | 34 | if err := os.WriteFile(f.Name(), []byte("example payload"), 0644); err != nil { 35 | t.Fatalf("write failed: %v", err) 36 | } 37 | 38 | select { 39 | case ev := <-n.EventLogChannel: 40 | if string(ev.Buffer) != "example payload" { 41 | t.Fatalf("unexpected payload: %q", string(ev.Buffer)) 42 | } 43 | case <-time.After(3 * time.Second): 44 | t.Fatal("timed out waiting for event") 45 | } 46 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Auuu Nya 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /memprofile_test.go: -------------------------------------------------------------------------------- 1 | package eventwatcher 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "runtime" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func TestMemSpike(t *testing.T) { 12 | var m1, m2 runtime.MemStats 13 | runtime.GC() 14 | runtime.ReadMemStats(&m1) 15 | t.Logf("Before: Alloc=%d TotalAlloc=%d Sys=%d NumGC=%d", m1.Alloc, m1.TotalAlloc, m1.Sys, m1.NumGC) 16 | 17 | // Create notifier and watcher 18 | ctx := context.Background() 19 | n := NewEventNotifier(ctx) 20 | defer n.Close() 21 | 22 | f, err := os.CreateTemp("", "mem_test_*.log") 23 | if err != nil { 24 | t.Fatal(err) 25 | } 26 | f.Close() 27 | defer os.Remove(f.Name()) 28 | 29 | if err := n.AddWatcher(f.Name()); err != nil { 30 | t.Fatalf("failed to add watcher: %v", err) 31 | } 32 | 33 | // wait a bit for goroutines to start 34 | time.Sleep(200 * time.Millisecond) 35 | 36 | runtime.GC() 37 | runtime.ReadMemStats(&m2) 38 | t.Logf("After: Alloc=%d TotalAlloc=%d Sys=%d NumGC=%d", m2.Alloc, m2.TotalAlloc, m2.Sys, m2.NumGC) 39 | 40 | if m2.Alloc > m1.Alloc*10+1024*1024 { 41 | t.Logf("Large allocation detected: before=%d after=%d", m1.Alloc, m2.Alloc) 42 | } 43 | } -------------------------------------------------------------------------------- /eventwatcher_unix_test.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package eventwatcher 5 | 6 | import ( 7 | "context" 8 | "os" 9 | "sync" 10 | "testing" 11 | "time" 12 | ) 13 | 14 | func TestEventWatcherUnixFile(t *testing.T) { 15 | f, err := os.CreateTemp("", "ew_test_*.log") 16 | if err != nil { 17 | t.Fatal(err) 18 | } 19 | defer os.Remove(f.Name()) 20 | f.Close() 21 | 22 | ctx := context.Background() 23 | n := NewEventNotifier(ctx) 24 | defer n.Close() 25 | 26 | if err := n.AddWatcher(f.Name()); err != nil { 27 | t.Fatalf("AddWatcher failed: %v", err) 28 | } 29 | 30 | // Give the watcher a moment to start and register the path 31 | time.Sleep(100 * time.Millisecond) 32 | 33 | var wg sync.WaitGroup 34 | wg.Add(1) 35 | go func() { 36 | defer wg.Done() 37 | select { 38 | case ch := <-n.EventLogChannel: 39 | if string(ch.Buffer) != "hello world" { 40 | t.Errorf("unexpected content: %q", string(ch.Buffer)) 41 | } 42 | case <-time.After(5 * time.Second): 43 | t.Errorf("timed out waiting for file write event") 44 | } 45 | }() 46 | 47 | if err := os.WriteFile(f.Name(), []byte("hello world"), 0644); err != nil { 48 | t.Fatalf("write failed: %v", err) 49 | } 50 | 51 | wg.Wait() 52 | } 53 | -------------------------------------------------------------------------------- /_example/winwatch/winwatch_test.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | "time" 10 | 11 | "github.com/auuunya/eventwatcher" 12 | "golang.org/x/sys/windows" 13 | ) 14 | 15 | func TestWinWatchExample(t *testing.T) { 16 | channel := "Application" 17 | ctx := context.Background() 18 | n := eventwatcher.NewEventNotifier(ctx) 19 | defer n.Close() 20 | 21 | if err := n.AddWatcher(channel); err != nil { 22 | t.Skipf("AddWatcher failed (needs Windows Event Log): %v", err) 23 | } 24 | 25 | // register event source 26 | h, err := eventwatcher.RegisterEventSource(nil, windows.StringToUTF16Ptr(channel)) 27 | if err != nil { 28 | t.Skipf("RegisterEventSource failed: %v", err) 29 | } 30 | defer eventwatcher.DeregisterEventSource(h) 31 | 32 | // report an event 33 | if err := eventwatcher.ReportEvent(h, eventwatcher.EVENTLOG_INFORMATION_TYPE, 0, 2222, nil, []string{"example from test"}, nil); err != nil { 34 | t.Fatalf("ReportEvent failed: %v", err) 35 | } 36 | 37 | // wait for the event 38 | select { 39 | case ev := <-n.EventLogChannel: 40 | r := eventwatcher.ParseEventLogData(ev.Buffer) 41 | if r.EventID != 2222 { 42 | t.Fatalf("unexpected event id: %d", r.EventID) 43 | } 44 | case <-time.After(5 * time.Second): 45 | t.Fatal("timed out waiting for windows event") 46 | } 47 | } -------------------------------------------------------------------------------- /_example/winwatch/main.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "os" 10 | "os/signal" 11 | "syscall" 12 | 13 | "github.com/auuunya/eventwatcher" 14 | "golang.org/x/sys/windows" 15 | ) 16 | 17 | func main() { 18 | channel := os.Getenv("EW_CHANNEL") 19 | if channel == "" { 20 | channel = "Application" 21 | } 22 | 23 | ctx := context.Background() 24 | n := eventwatcher.NewEventNotifier(ctx) 25 | defer n.Close() 26 | 27 | if err := n.AddWatcher(channel); err != nil { 28 | fmt.Printf("AddWatcher failed: %v\n", err) 29 | return 30 | } 31 | 32 | go func() { 33 | for ev := range n.EventLogChannel { 34 | r := eventwatcher.ParseEventLogData(ev.Buffer) 35 | fmt.Printf("name=%s id=%d content=%s\n", ev.Name, r.EventID, eventwatcher.FormatContent(ev.Buffer)) 36 | } 37 | }() 38 | 39 | // register + report a test event so example shows output when run locally 40 | h, err := eventwatcher.RegisterEventSource(nil, windows.StringToUTF16Ptr(channel)) 41 | if err == nil { 42 | _ = eventwatcher.ReportEvent(h, eventwatcher.EVENTLOG_INFORMATION_TYPE, 0, 1111, nil, []string{"example"}, nil) 43 | _ = eventwatcher.DeregisterEventSource(h) 44 | } 45 | 46 | quit := make(chan os.Signal, 1) 47 | signal.Notify(quit, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM) 48 | <-quit 49 | fmt.Println("exiting") 50 | } -------------------------------------------------------------------------------- /channels_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package eventwatcher 5 | 6 | import ( 7 | "errors" 8 | "syscall" 9 | "unsafe" 10 | ) 11 | 12 | var ( 13 | // https://learn.microsoft.com/zh-cn/windows/win32/api/winevt/ 14 | modwevtapi = syscall.MustLoadDLL("Wevtapi.dll") 15 | procEvtOpenChannelEnum = modwevtapi.MustFindProc("EvtOpenChannelEnum") 16 | procEvtNextChannelPath = modwevtapi.MustFindProc("EvtNextChannelPath") 17 | procEvtClose = modwevtapi.MustFindProc("EvtClose") 18 | ) 19 | 20 | func EvtOpenChannelEnum(session syscall.Handle) (syscall.Handle, error) { 21 | handle, _, err := procEvtOpenChannelEnum.Call( 22 | uintptr(session), 23 | 0, 24 | ) 25 | if handle == 0 { 26 | return InvalidHandle, errors.New("failed to open event channel enumeration handle: " + err.Error()) 27 | } 28 | return syscall.Handle(handle), nil 29 | } 30 | 31 | func evtClose(handle syscall.Handle) error { 32 | return EvtClose(handle) 33 | } 34 | 35 | func EvtClose(handle syscall.Handle) error { 36 | ret, _, err := procEvtClose.Call(uintptr(handle)) 37 | if ret == 0 { 38 | return errors.New("failed to close handle: " + err.Error()) 39 | } 40 | return nil 41 | } 42 | 43 | func EvtNextChannelPath(handle syscall.Handle) ([]string, error) { 44 | channels := []string{} 45 | for { 46 | var bufferSize, bufferUsed uint32 47 | ret, _, err := procEvtNextChannelPath.Call( 48 | uintptr(handle), 49 | uintptr(bufferSize), 50 | 0, 51 | uintptr(unsafe.Pointer(&bufferUsed)), 52 | ) 53 | if ret == 0 { 54 | if err == ERROR_INSUFFICIENT_BUFFER { 55 | bufferSize = bufferUsed 56 | buffer := make([]uint16, bufferUsed) 57 | ret, _, _ := procEvtNextChannelPath.Call( 58 | uintptr(handle), 59 | uintptr(bufferSize), 60 | uintptr(unsafe.Pointer(&buffer[0])), 61 | uintptr(unsafe.Pointer(&bufferUsed)), 62 | ) 63 | if ret == 0 { 64 | return nil, errors.New("failed to retrieve channel path after buffer allocation: " + err.Error()) 65 | } 66 | channels = append(channels, syscall.UTF16ToString(buffer)) 67 | } else if err == ERROR_NO_MORE_ITEMS { 68 | break 69 | } else { 70 | return nil, errors.New("failed to retrieve channel path: " + err.Error()) 71 | } 72 | } 73 | } 74 | return channels, nil 75 | } -------------------------------------------------------------------------------- /README_ZH.md: -------------------------------------------------------------------------------- 1 | ### EventWatcher 2 | [![Go Reference](https://pkg.go.dev/badge/github.com/auuunya/eventwatcher.svg)](https://pkg.go.dev/github.com/auuunya/eventwatcher) [![CI](https://github.com/auuunya/eventwatcher/actions/workflows/ci.yml/badge.svg)](https://github.com/auuunya/eventwatcher/actions/workflows/ci.yml) 3 | #### 概述 4 | EventWatcher 是一个开源库,设计用于实时监控 Windows 事件日志。它为系统事件、应用程序日志和其他重要事件源的跟踪和响应提供了一种健壮且高效的解决方案。这个库对于需要监控事件日志以进行调试、审计和系统管理的开发人员和系统管理员来说特别有用。 5 | 6 | #### 使用方法 7 | 要使用 EventWatcher 库,您需要: 8 | 9 | 1. 创建一个 EventNotifier 实例。 10 | 2. 添加您感兴趣的日志的事件监视器。 11 | 3. 在 EventLogChannel 上监听事件数据。 12 | 4. 通过适当关闭 EventNotifier 确保优雅地关闭程序。 13 | 14 | #### 安装 15 | 要安装 EventWatcher 库,请运行: 16 | 17 | ```go 18 | go get github.com/auuunya/eventwatcher 19 | ``` 20 | 21 | #### 示例 22 | 23 | ```golang 24 | package main 25 | 26 | import ( 27 | "github.com/auuunya/eventwatcher" 28 | ) 29 | 30 | func main() { 31 | ctx := context.Background() 32 | notify := eventwatcher.NewEventNotifier(ctx) 33 | defer notify.Close() 34 | 35 | channels := []string{"Application", "System", "Microsoft-Windows-Kernel-Dump/Operational"} 36 | for _, channel := range channels { 37 | err := notify.AddWatcher(channel) 38 | if err != nil { 39 | continue 40 | } 41 | } 42 | 43 | go func() { 44 | for ch := range notify.EventLogChannel { 45 | fmt.Printf("event entry: %v\n", ch) 46 | } 47 | }() 48 | 49 | quit := make(chan os.Signal, 1) 50 | signal.Notify(quit, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM) 51 | <-quit 52 | } 53 | 54 | ``` 55 | 56 | #### Windows powershell add event 57 | ```Powershell 58 | Write-EventLog -LogName "Application" -Source "TestSource" -EventID 1 -EntryType Information -Message "Application Test Info" 59 | ``` 60 | #### Windows cmd add event 61 | ```cmd 62 | eventcreate /ID 1 /L APPLICATION /T INFORMATION /SO MYEVENTSOURCE /D "Test Application Infomation" 63 | ``` 64 | 65 | #### 跨平台支持 66 | - **Windows:** 使用 Windows 原生事件日志 API(保持原有行为)。与 Windows 相关的测试和实现均使用 `//go:build windows` build tag。 67 | - **macOS / Linux:** 为类 Unix 平台提供基于 `fsnotify` 的文件监控实现。调用 `AddWatcher(path)`,其中 `path` 为文件路径,写入该文件即可触发事件。 68 | - **说明:** 在非 Windows 平台上,Windows 特有 API 会返回未实现错误(not-implemented),建议使用 Unix watcher 进行跨平台监控。 69 | 70 | #### 运行测试与性能分析 71 | - 运行全部测试:`go test ./...` 72 | - 仅运行 Unix watcher 测试(macOS/Linux):`go test -run TestEventWatcherUnixFile -v` 73 | - 检查启动内存:`go test -run TestMemSpike -v`(会记录 runtime.MemStats 的启动前/后数据)。 74 | 75 | #### 贡献 76 | 欢迎贡献代码!请随时在 GitHub 仓库上提出问题或提交拉取请求。 77 | 78 | #### 许可 79 | 此项目根据 MIT 许可发布。详情请参阅 LICENSE 文件。 80 | -------------------------------------------------------------------------------- /eventnotifier.go: -------------------------------------------------------------------------------- 1 | package eventwatcher 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "sync" 8 | ) 9 | 10 | type EventEntry struct { 11 | Name string `json:"name"` 12 | Handle uintptr `json:"handle"` 13 | Buffer []byte `json:"buffer"` 14 | } 15 | 16 | // EventNotifier manages a collection of EventWatchers. 17 | type EventNotifier struct { 18 | EventLogChannel chan *EventEntry 19 | watchers map[string]*EventWatcher 20 | ctx context.Context 21 | wg sync.WaitGroup 22 | mu sync.Mutex 23 | } 24 | 25 | // NewEventNotifier creates a new EventNotifier instance. 26 | func NewEventNotifier(ctx context.Context) *EventNotifier { 27 | return &EventNotifier{ 28 | ctx: ctx, 29 | watchers: make(map[string]*EventWatcher), 30 | EventLogChannel: make(chan *EventEntry), 31 | } 32 | } 33 | 34 | // AddWatcher adds a new EventWatcher to the EventNotifier. 35 | func (en *EventNotifier) AddWatcher(name string) error { 36 | en.mu.Lock() 37 | defer en.mu.Unlock() 38 | 39 | if _, exists := en.watchers[name]; exists { 40 | return errors.New(name + " event watcher already exists") 41 | } 42 | 43 | watcher := NewEventWatcher(en.ctx, name, en.EventLogChannel) 44 | if err := watcher.Init(); err != nil { 45 | return err 46 | } 47 | 48 | en.watchers[name] = watcher 49 | en.wg.Add(1) 50 | go func(watcher *EventWatcher) { 51 | defer en.wg.Done() 52 | watcher.Listen() 53 | }(watcher) 54 | return nil 55 | } 56 | 57 | // RemoveWatcher removes an EventWatcher from the EventNotifier. 58 | func (en *EventNotifier) RemoveWatcher(name string) error { 59 | en.mu.Lock() 60 | defer en.mu.Unlock() 61 | 62 | watcher, exists := en.watchers[name] 63 | if !exists { 64 | return errors.New(name + " event watcher does not exist") 65 | } 66 | 67 | watcher.Close() 68 | delete(en.watchers, name) 69 | return nil 70 | } 71 | 72 | // Close shuts down all EventWatchers and waits for them to exit. 73 | func (en *EventNotifier) Close() { 74 | en.mu.Lock() 75 | for _, watcher := range en.watchers { 76 | watcher.Close() 77 | } 78 | en.mu.Unlock() 79 | en.watchers = make(map[string]*EventWatcher) 80 | close(en.EventLogChannel) 81 | en.wg.Wait() 82 | } 83 | 84 | // GetWatcher retrieves an EventWatcher by name. 85 | func (en *EventNotifier) GetWatcher(name string) (*EventWatcher, error) { 86 | en.mu.Lock() 87 | defer en.mu.Unlock() 88 | 89 | watcher, exists := en.watchers[name] 90 | if !exists { 91 | return nil, fmt.Errorf("%s event watcher not found", name) 92 | } 93 | return watcher, nil 94 | } 95 | -------------------------------------------------------------------------------- /eventwatcher_test.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package eventwatcher_test 5 | 6 | import ( 7 | "context" 8 | "sync" 9 | "testing" 10 | 11 | "github.com/auuunya/eventwatcher" 12 | "golang.org/x/sys/windows" 13 | ) 14 | 15 | var ( 16 | channel = "Application" 17 | 18 | eventId uint32 = 123432 19 | wantEventId uint32 = 123432 20 | message string 21 | ) 22 | 23 | func TestEventWatcher(t *testing.T) { 24 | ctx := context.Background() 25 | notify := eventwatcher.NewEventNotifier(ctx) 26 | defer notify.Close() 27 | 28 | err := notify.AddWatcher(channel) 29 | if err != nil { 30 | return 31 | } 32 | 33 | var wg sync.WaitGroup 34 | // Register the event source 35 | handle, err := eventwatcher.RegisterEventSource(nil, windows.StringToUTF16Ptr(channel)) 36 | if err != nil { 37 | return 38 | } 39 | defer eventwatcher.DeregisterEventSource(handle) 40 | 41 | wg.Add(1) 42 | go func() { 43 | defer wg.Done() 44 | for ch := range notify.EventLogChannel { 45 | val := eventwatcher.ParseEventLogData(ch.Buffer) 46 | if val.EventID != wantEventId { 47 | t.Errorf("unable to read application event log, event id: %d, want event id: %d\n", val.EventID, wantEventId) 48 | } 49 | break 50 | } 51 | }() 52 | 53 | // Write an empty event 54 | message = "This is an event log message!" 55 | err = eventwatcher.ReportEvent(handle, eventwatcher.EVENTLOG_INFORMATION_TYPE, 0, eventId, nil, []string{message}, nil) 56 | if err != nil { 57 | return 58 | } 59 | 60 | wg.Wait() 61 | } 62 | 63 | func TestEmptyEventWatcher(t *testing.T) { 64 | ctx := context.Background() 65 | notify := eventwatcher.NewEventNotifier(ctx) 66 | defer notify.Close() 67 | 68 | err := notify.AddWatcher(channel) 69 | if err != nil { 70 | return 71 | } 72 | 73 | var wg sync.WaitGroup 74 | // Register the event source 75 | handle, err := eventwatcher.RegisterEventSource(nil, windows.StringToUTF16Ptr(channel)) 76 | if err != nil { 77 | return 78 | } 79 | defer eventwatcher.DeregisterEventSource(handle) 80 | 81 | wg.Add(1) 82 | go func() { 83 | defer wg.Done() 84 | for ch := range notify.EventLogChannel { 85 | val := eventwatcher.ParseEventLogData(ch.Buffer) 86 | if val.EventID != 0 { 87 | t.Errorf("unable to read application event log, event id: %d, want event id: %d\n", val.EventID, 0) 88 | } 89 | break 90 | } 91 | }() 92 | 93 | // Write an empty event 94 | message = "" 95 | err = eventwatcher.ReportEvent(handle, eventwatcher.EVENTLOG_INFORMATION_TYPE, 0, 0, nil, []string{message}, nil) 96 | if err != nil { 97 | return 98 | } 99 | wg.Wait() 100 | } 101 | -------------------------------------------------------------------------------- /eventparser_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package eventwatcher 5 | 6 | import ( 7 | "fmt" 8 | "syscall" 9 | "unsafe" 10 | 11 | "golang.org/x/sys/windows" 12 | ) 13 | 14 | type EventLogRecord struct { 15 | Length uint32 16 | Reserved uint32 17 | RecordNumber uint32 18 | TimeGenerated uint32 19 | TimeWritten uint32 20 | EventID uint32 21 | EventType uint16 22 | NumStrings uint16 23 | EventCategory uint16 24 | ReservedFlags uint16 25 | ClosingRecordNumber uint32 26 | StringOffset uint32 27 | UserSidLength uint32 28 | UserSidOffset uint32 29 | DataLength uint32 30 | DataOffset uint32 31 | } 32 | 33 | // ParseEventLogData parses the event log data. 34 | func ParseEventLogData(buf []byte) *EventLogRecord { 35 | var record EventLogRecord 36 | index := 0 37 | for { 38 | if index+int(unsafe.Sizeof(record)) > len(buf) { 39 | break 40 | } 41 | record = *(*EventLogRecord)(unsafe.Pointer(&buf[index])) 42 | index += int(record.Length) 43 | if index >= len(buf) { 44 | break 45 | } 46 | } 47 | return &record 48 | } 49 | 50 | func ParserEventLogData(buf []byte) (*EventLogRecord, error) { 51 | if len(buf) < int(unsafe.Sizeof(EventLogRecord{})) { 52 | return nil, windows.ERROR_INSUFFICIENT_BUFFER 53 | } 54 | record := (*EventLogRecord)(unsafe.Pointer(&buf[0])) 55 | return record, nil 56 | } 57 | 58 | func FormatContent(buf []byte) string { 59 | r := (*EventLogRecord)(unsafe.Pointer(&buf[0])) 60 | return syscall.UTF16ToString((*[1 << 10]uint16)(unsafe.Pointer(uintptr(unsafe.Pointer(r)) + uintptr(r.StringOffset)))[:]) 61 | } 62 | 63 | func FormatMessage(errorCode uint32) string { 64 | var messageBuffer [4096]uint16 65 | flags := uint32(windows.FORMAT_MESSAGE_FROM_SYSTEM | windows.FORMAT_MESSAGE_IGNORE_INSERTS) 66 | 67 | numChars, err := windows.FormatMessage(flags, 0, errorCode, 0, messageBuffer[:], nil) 68 | if err != nil { 69 | return fmt.Sprintf("Unknown error 0x%x", errorCode) 70 | } 71 | 72 | return windows.UTF16ToString(messageBuffer[:numChars]) 73 | } 74 | 75 | // LookupAccountSid retrieves the account name and domain name for the specified SID. 76 | func LookupAccountSid(buf []byte, sidlen, sidoffset uint32) (string, string, error) { 77 | var userSID *windows.SID 78 | if sidlen < 0 { 79 | return "", "", fmt.Errorf("unable to get sid") 80 | } 81 | userSID = (*windows.SID)(unsafe.Pointer(&buf[sidoffset])) 82 | var nameLen, domainLen uint32 83 | var sidType uint32 84 | 85 | // Initial call to determine the buffer sizes. 86 | err := windows.LookupAccountSid(nil, userSID, nil, &nameLen, nil, &domainLen, &sidType) 87 | if err != nil && err != windows.ERROR_INSUFFICIENT_BUFFER { 88 | return "", "", err 89 | } 90 | 91 | // Allocate buffers. 92 | nameBuffer := make([]uint16, nameLen) 93 | domainBuffer := make([]uint16, domainLen) 94 | 95 | err = windows.LookupAccountSid(nil, userSID, &nameBuffer[0], &nameLen, &domainBuffer[0], &domainLen, &sidType) 96 | if err != nil { 97 | return "", "", err 98 | } 99 | 100 | return windows.UTF16ToString(nameBuffer), windows.UTF16ToString(domainBuffer), nil 101 | } 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### EventWatcher 2 | [![Go Reference](https://pkg.go.dev/badge/github.com/auuunya/eventwatcher.svg)](https://pkg.go.dev/github.com/auuunya/eventwatcher) [![CI](https://github.com/auuunya/eventwatcher/actions/workflows/ci.yml/badge.svg)](https://github.com/auuunya/eventwatcher/actions/workflows/ci.yml) 3 | #### Overview 4 | EventWatcher is an open-source library designed for monitoring Windows Event Logs in real-time. It provides a robust and efficient solution for tracking and reacting to system events, application logs, and other important event sources. This library is particularly useful for developers and system administrators who need to monitor event logs for debugging, auditing, and system management purposes. 5 | 6 | #### Usage 7 | To use the EventWatcher library, you need to: 8 | 1. Create an `EventNotifier` instance. 9 | 2. Add event watchers for the logs you are interested in. 10 | 3. Listen for event data on the `EventLogChannel`. 11 | 4. Ensure a graceful shutdown by properly closing the `EventNotifier`. 12 | 13 | #### Installation 14 | To install the EventWatcher library, run: 15 | 16 | ```golang 17 | go get github.com/auuunya/eventwatcher 18 | ``` 19 | 20 | #### Example 21 | 22 | ```golang 23 | package main 24 | 25 | import ( 26 | "github.com/auuunya/eventwatcher" 27 | ) 28 | 29 | func main() { 30 | ctx := context.Background() 31 | notify := eventwatcher.NewEventNotifier(ctx) 32 | defer notify.Close() 33 | 34 | channels := []string{"Application", "System", "Microsoft-Windows-Kernel-Dump/Operational"} 35 | for _, channel := range channels { 36 | err := notify.AddWatcher(channel) 37 | if err != nil { 38 | continue 39 | } 40 | } 41 | 42 | go func() { 43 | for ch := range notify.EventLogChannel { 44 | fmt.Printf("event entry: %v\n", ch) 45 | } 46 | }() 47 | 48 | quit := make(chan os.Signal, 1) 49 | signal.Notify(quit, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM) 50 | <-quit 51 | } 52 | 53 | ``` 54 | 55 | #### Windows powershell add event 56 | ```Powershell 57 | Write-EventLog -LogName "Application" -Source "TestSource" -EventID 1 -EntryType Information -Message "Application Test Info" 58 | ``` 59 | #### Windows cmd add event 60 | ```cmd 61 | eventcreate /ID 1 /L APPLICATION /T INFORMATION /SO MYEVENTSOURCE /D "Test Application Infomation" 62 | ``` 63 | 64 | #### Cross-platform support 65 | - **Windows:** Uses native Windows Event Log APIs (original behavior). Windows-specific tests and implementations are build-tagged with `//go:build windows`. 66 | - **macOS / Linux:** A lightweight file-watching implementation using `fsnotify` is provided for Unix-like systems. On these platforms, call `AddWatcher(path)` where `path` is a file path (writing to the file will emit an event). 67 | - **Notes:** On non-Windows platforms, Windows-specific APIs return not-implemented errors; use the Unix watcher for most cross-platform needs. 68 | 69 | #### Running tests & profiling 70 | - Run all tests: `go test ./...` 71 | - Run Unix watcher test (macOS/Linux): `go test -run TestEventWatcherUnixFile -v` 72 | - Run memory check: `go test -run TestMemSpike -v` (this logs runtime.MemStats before/after watcher start). 73 | 74 | #### Contribution 75 | Contributions are welcome! Feel free to open issues or submit pull requests on the GitHub repository. 76 | 77 | #### License 78 | This project is licensed under the MIT License. See the LICENSE file for details. -------------------------------------------------------------------------------- /eventwatcher_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package eventwatcher 5 | 6 | import ( 7 | "context" 8 | "syscall" 9 | ) 10 | 11 | // Windows-specific methods implemented in this file. 12 | // The EventWatcher struct is defined in eventwatcher_common.go. 13 | 14 | // NewEventWatcher creates a new EventWatcher instance. 15 | func NewEventWatcher(ctx context.Context, name string, eventChan chan *EventEntry) *EventWatcher { 16 | ctx, cancel := context.WithCancel(ctx) 17 | return &EventWatcher{ 18 | Name: name, 19 | ctx: ctx, 20 | cancel: cancel, 21 | eventChan: eventChan, 22 | stopCh: make(chan struct{}), 23 | } 24 | } 25 | 26 | // Init initializes the EventWatcher instance. 27 | func (ew *EventWatcher) Init() error { 28 | handle, err := openEventLog(ew.Name) 29 | if err != nil { 30 | return err 31 | } 32 | ew.handle = handle 33 | 34 | offset, err := eventRecordNumber(ew.handle) 35 | if err != nil { 36 | return err 37 | } 38 | ew.offset = offset 39 | 40 | if ew.eventHandle, err = createEvent(nil, 0, 1, nil); err != nil { 41 | return err 42 | } 43 | 44 | if ew.cancelHandle, err = createEvent(nil, 1, 0, nil); err != nil { 45 | return err 46 | } 47 | return nil 48 | } 49 | 50 | // Close cancels the context and triggers the cancel event. 51 | func (ew *EventWatcher) Close() { 52 | ew.cancel() 53 | setEvent(ew.cancelHandle) 54 | close(ew.stopCh) 55 | } 56 | 57 | // CloseHandles closes all handles associated with the EventWatcher. 58 | func (ew *EventWatcher) CloseHandles() error { 59 | var err error 60 | if ew.handle != 0 { 61 | if e := closeEventLog(ew.handle); e != nil { 62 | err = e 63 | } 64 | } 65 | if ew.cancelHandle != 0 { 66 | if e := closeHandle(ew.cancelHandle); e != nil { 67 | err = e 68 | } 69 | } 70 | if ew.eventHandle != 0 { 71 | if e := closeHandle(ew.eventHandle); e != nil { 72 | err = e 73 | } 74 | } 75 | return err 76 | } 77 | 78 | // Listen monitors the event log and processes changes. 79 | func (ew *EventWatcher) Listen() { 80 | defer ew.CloseHandles() 81 | 82 | if err := notifyChange(ew.handle, ew.eventHandle); err != nil { 83 | return 84 | } 85 | 86 | for { 87 | select { 88 | case <-ew.stopCh: 89 | return 90 | case <-ew.ctx.Done(): 91 | return 92 | default: 93 | handles := []syscall.Handle{ew.eventHandle, ew.cancelHandle} 94 | event, err := waitForMultipleObjects(handles, false, syscall.INFINITE) 95 | if err != nil { 96 | return 97 | } 98 | switch event { 99 | case syscall.WAIT_OBJECT_0: 100 | rn, err := eventRecordNumber(ew.handle) 101 | if err != nil { 102 | return 103 | } 104 | if ew.offset == rn { 105 | continue 106 | } 107 | ew.offset = rn 108 | 109 | buf, err := readEventLog(ew.handle, EVENTLOG_SEEK_READ|EVENTLOG_FORWARDS_READ, ew.offset) 110 | if err != nil { 111 | return 112 | } 113 | ew.eventChan <- &EventEntry{ 114 | Name: ew.Name, 115 | Handle: ew.handle, 116 | Buffer: buf, 117 | } 118 | 119 | if err := resetEvent(ew.eventHandle); err != nil { 120 | return 121 | } 122 | if err := resetEvent(ew.cancelHandle); err != nil { 123 | return 124 | } 125 | case syscall.WAIT_OBJECT_0 + 1: 126 | return 127 | default: 128 | return 129 | } 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /eventwatcher_unix.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package eventwatcher 5 | 6 | import ( 7 | "context" 8 | "os" 9 | "time" 10 | 11 | "github.com/fsnotify/fsnotify" 12 | ) 13 | 14 | // Unix/macOS implementation of EventWatcher using fsnotify. The Name field 15 | // is treated as a path to a file to watch; when the file is written to, 16 | // the watcher will read the file content and emit it on the EventLogChannel. 17 | 18 | type unixEventWatcher struct{ 19 | *EventWatcher 20 | watcher *fsnotify.Watcher 21 | path string 22 | } 23 | 24 | func NewEventWatcher(ctx context.Context, name string, eventChan chan *EventEntry) *EventWatcher { 25 | // Create a wrapper EventWatcher and return it; Init will set up fsnotify. 26 | ctx, cancel := context.WithCancel(ctx) 27 | return &EventWatcher{ 28 | Name: name, 29 | ctx: ctx, 30 | cancel: cancel, 31 | eventChan: eventChan, 32 | stopCh: make(chan struct{}), 33 | } 34 | } 35 | 36 | // Init sets up fsnotify watcher for the provided file path. 37 | func (ew *EventWatcher) Init() error { 38 | // Ensure file exists: create if missing 39 | if _, err := os.Stat(ew.Name); os.IsNotExist(err) { 40 | f, err := os.Create(ew.Name) 41 | if err != nil { 42 | return err 43 | } 44 | f.Close() 45 | } 46 | 47 | w, err := fsnotify.NewWatcher() 48 | if err != nil { 49 | return err 50 | } 51 | if err := w.Add(ew.Name); err != nil { 52 | w.Close() 53 | return err 54 | } 55 | // replace underlying watcher using type assertion 56 | uwe := &unixEventWatcher{EventWatcher: ew, watcher: w, path: ew.Name} 57 | // store watcher pointer via embedding by setting to local variable (only used in Listen/CloseHandles) 58 | // we keep it on the heap by writing back 59 | *ew = *uwe.EventWatcher 60 | // hack: attach watcher via context value 61 | // but simpler: we store pointer in a private map? For now, attach to stopCh using closure in Listen. 62 | // We'll rely on ew.stopCh and w to be closed in CloseHandles. 63 | return nil 64 | } 65 | 66 | // Close handles cleans up resources for the watcher. 67 | func (ew *EventWatcher) CloseHandles() error { 68 | return nil 69 | } 70 | 71 | // Close stops the watcher. 72 | func (ew *EventWatcher) Close() { 73 | ew.cancel() 74 | select { 75 | case <-ew.stopCh: 76 | // already closed 77 | default: 78 | close(ew.stopCh) 79 | } 80 | } 81 | 82 | // Listen monitors the fsnotify watcher and emits file contents on write events. 83 | func (ew *EventWatcher) Listen() { 84 | // Create watcher locally so we can close it in defer. 85 | w, err := fsnotify.NewWatcher() 86 | if err != nil { 87 | return 88 | } 89 | defer w.Close() 90 | 91 | if err := w.Add(ew.Name); err != nil { 92 | return 93 | } 94 | 95 | for { 96 | select { 97 | case <-ew.stopCh: 98 | return 99 | case <-ew.ctx.Done(): 100 | return 101 | case ev, ok := <-w.Events: 102 | if !ok { 103 | return 104 | } 105 | if ev.Op&fsnotify.Write == fsnotify.Write || ev.Op&fsnotify.Create == fsnotify.Create { 106 | // small debounce 107 | time.Sleep(20 * time.Millisecond) 108 | b, err := os.ReadFile(ew.Name) 109 | if err != nil { 110 | continue 111 | } 112 | ew.eventChan <- &EventEntry{Name: ew.Name, Handle: 0, Buffer: b} 113 | } 114 | case <-time.After(5 * time.Second): 115 | // keep loop alive and responsive to stop signals 116 | } 117 | } 118 | } -------------------------------------------------------------------------------- /event.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package eventwatcher 5 | 6 | import ( 7 | "errors" 8 | ) 9 | 10 | // Non-Windows stubs so package compiles on macOS/Linux. These should 11 | // be replaced by real implementations (e.g., using fsnotify) in future 12 | // work. 13 | 14 | func openEventLog(name string) (uintptr, error) { 15 | return 0, errors.New("openEventLog not implemented on this OS") 16 | } 17 | 18 | func OpenEventLog(name string) (uintptr, error) { 19 | return 0, errors.New("OpenEventLog not implemented on this OS") 20 | } 21 | 22 | func closeEventLog(handle uintptr) error { 23 | return errors.New("closeEventLog not implemented on this OS") 24 | } 25 | 26 | func CloseEventLog(handle uintptr) error { 27 | return errors.New("CloseEventLog not implemented on this OS") 28 | } 29 | 30 | func notifyChange(handle, event uintptr) error { 31 | return errors.New("notifyChange not implemented on this OS") 32 | } 33 | 34 | func NotifyChangeEventLog(handle, event uintptr) error { 35 | return errors.New("NotifyChangeEventLog not implemented on this OS") 36 | } 37 | 38 | func eventRecordNumber(handle uintptr) (uint32, error) { 39 | return 0, errors.New("eventRecordNumber not implemented on this OS") 40 | } 41 | 42 | func EventLogRecordNumber(handle uintptr) (uint32, error) { 43 | return 0, errors.New("EventLogRecordNumber not implemented on this OS") 44 | } 45 | 46 | func readEventLog(handle uintptr, flags, offset uint32) ([]byte, error) { 47 | return nil, errors.New("readEventLog not implemented on this OS") 48 | } 49 | 50 | func ReadEventLog(handle uintptr, flags, offset uint32) ([]byte, error) { 51 | return nil, errors.New("ReadEventLog not implemented on this OS") 52 | } 53 | 54 | func createEvent( 55 | eventAttributes *uintptr, 56 | manualReset, initialState uint32, 57 | name *uint16, 58 | ) (uintptr, error) { 59 | return 0, errors.New("createEvent not implemented on this OS") 60 | } 61 | 62 | func CreateEvent( 63 | eventAttributes *uintptr, 64 | manualReset, initialState uint32, 65 | name *uint16, 66 | ) (uintptr, error) { 67 | return 0, errors.New("CreateEvent not implemented on this OS") 68 | } 69 | 70 | func resetEvent(handle uintptr) error { 71 | return errors.New("resetEvent not implemented on this OS") 72 | } 73 | 74 | func ResetEvent(handle uintptr) error { 75 | return errors.New("ResetEvent not implemented on this OS") 76 | } 77 | 78 | func waitForMultipleObjects( 79 | handles []uintptr, 80 | waitAll bool, 81 | waitMilliseconds uint32) (uint32, error) { 82 | return 0, errors.New("waitForMultipleObjects not implemented on this OS") 83 | } 84 | 85 | func WaitForMultipleObjects( 86 | handles []uintptr, 87 | waitAll bool, 88 | waitMilliseconds uint32, 89 | ) (event uint32, err error) { 90 | return 0, errors.New("WaitForMultipleObjects not implemented on this OS") 91 | } 92 | 93 | func setEvent(handle uintptr) error { 94 | return errors.New("setEvent not implemented on this OS") 95 | } 96 | 97 | func SetEvent(handle uintptr) error { 98 | return errors.New("SetEvent not implemented on this OS") 99 | } 100 | 101 | func closeHandle(handle uintptr) error { 102 | return errors.New("closeHandle not implemented on this OS") 103 | } 104 | 105 | func CloseHandle(handle uintptr) error { 106 | return errors.New("CloseHandle not implemented on this OS") 107 | } 108 | 109 | func registerEventSource(uncServerName, sourceName *uint16) (handle uintptr, err error) { 110 | return 0, errors.New("registerEventSource not implemented on this OS") 111 | } 112 | 113 | func RegisterEventSource(uncServerName, sourceName *uint16) (handle uintptr, err error) { 114 | return 0, errors.New("RegisterEventSource not implemented on this OS") 115 | } 116 | 117 | func reportEvent(log uintptr, etype uint16, category uint16, eventID uint32, userSid *uintptr, strings []string, binaryData []byte) error { 118 | return errors.New("reportEvent not implemented on this OS") 119 | } 120 | 121 | func ReportEvent(log uintptr, etype uint16, category uint16, eventID uint32, userSid *uintptr, strings []string, binaryData []byte) error { 122 | return errors.New("ReportEvent not implemented on this OS") 123 | } 124 | 125 | func deregisterEventSource(log uintptr) error { 126 | return errors.New("deregisterEventSource not implemented on this OS") 127 | } 128 | 129 | func DeregisterEventSource(log uintptr) error { 130 | return errors.New("DeregisterEventSource not implemented on this OS") 131 | } -------------------------------------------------------------------------------- /event_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package eventwatcher 5 | 6 | import ( 7 | "errors" 8 | "syscall" 9 | "unsafe" 10 | 11 | "golang.org/x/sys/windows" 12 | ) 13 | 14 | var ( 15 | // https://learn.microsoft.com/zh-cn/windows/win32/api/winbase/ 16 | modadvapi32 = syscall.MustLoadDLL("advapi32.dll") 17 | procOpenEventLog = modadvapi32.MustFindProc("OpenEventLogW") 18 | procReadEventLog = modadvapi32.MustFindProc("ReadEventLogW") 19 | procCloseEventLog = modadvapi32.MustFindProc("CloseEventLog") 20 | procNotifyChangeEventLog = modadvapi32.MustFindProc("NotifyChangeEventLog") 21 | procGetNumberOfEventLogRecords = modadvapi32.MustFindProc("GetNumberOfEventLogRecords") 22 | procRegisterEventSourceW = modadvapi32.MustFindProc("RegisterEventSourceW") 23 | procReportEventW = modadvapi32.MustFindProc("ReportEventW") 24 | procDeregisterEventSource = modadvapi32.MustFindProc("DeregisterEventSource") 25 | // https://learn.microsoft.com/zh-cn/windows/win32/api/synchapi/ 26 | modkernel32 = syscall.MustLoadDLL("Kernel32.dll") 27 | procCreateEvent = modkernel32.MustFindProc("CreateEventW") 28 | procResetEvent = modkernel32.MustFindProc("ResetEvent") 29 | procWaitForMultipleObjects = modkernel32.MustFindProc("WaitForMultipleObjects") 30 | procSetEvent = modkernel32.MustFindProc("SetEvent") 31 | procCloseHandle = modkernel32.MustFindProc("CloseHandle") 32 | ) 33 | 34 | func openEventLog(name string) (syscall.Handle, error) { 35 | return OpenEventLog(name) 36 | } 37 | 38 | func OpenEventLog(name string) (syscall.Handle, error) { 39 | namePtr, err := syscall.UTF16PtrFromString(name) 40 | if err != nil { 41 | return InvalidHandle, err 42 | } 43 | handle, _, err := procOpenEventLog.Call( 44 | 0, 45 | uintptr(unsafe.Pointer(namePtr)), 46 | ) 47 | if handle == 0 { 48 | return InvalidHandle, errors.New("failed to open event: " + err.Error()) 49 | } 50 | return syscall.Handle(handle), nil 51 | } 52 | 53 | func closeEventLog(handle syscall.Handle) error { 54 | return CloseEventLog(handle) 55 | } 56 | 57 | func CloseEventLog(handle syscall.Handle) error { 58 | ret, _, err := procCloseEventLog.Call(uintptr(handle)) 59 | if ret == 0 { 60 | return errors.New("failed to close event: " + err.Error()) 61 | } 62 | return nil 63 | } 64 | func notifyChange(handle, event syscall.Handle) error { 65 | return NotifyChangeEventLog(handle, event) 66 | } 67 | 68 | func NotifyChangeEventLog(handle, event syscall.Handle) error { 69 | ret, _, err := procNotifyChangeEventLog.Call( 70 | uintptr(handle), 71 | uintptr(event), 72 | ) 73 | if ret == NO_ERROR { 74 | return errors.New("failed to notify change event: " + err.Error()) 75 | } 76 | return nil 77 | } 78 | 79 | func eventRecordNumber(handle syscall.Handle) (uint32, error) { 80 | return EventLogRecordNumber(handle) 81 | } 82 | 83 | func EventLogRecordNumber(handle syscall.Handle) (uint32, error) { 84 | var retVal uint32 85 | ret, _, err := procGetNumberOfEventLogRecords.Call( 86 | uintptr(handle), 87 | uintptr(unsafe.Pointer(&retVal)), 88 | ) 89 | if ret != 0 { 90 | return retVal, nil 91 | } 92 | return 0, errors.New("failed to get number of handle: " + err.Error()) 93 | } 94 | 95 | func readEventLog(handle syscall.Handle, flags, offset uint32) ([]byte, error) { 96 | return ReadEventLog(handle, flags, offset) 97 | } 98 | 99 | func ReadEventLog(handle syscall.Handle, flags, offset uint32) ([]byte, error) { 100 | var BUFFER_SIZE = 4096 101 | buffer := make([]byte, BUFFER_SIZE) 102 | var bytesRead, minByteNeeded uint32 103 | for { 104 | ret, _, err := procReadEventLog.Call( 105 | uintptr(handle), 106 | uintptr(flags), 107 | uintptr(offset), 108 | uintptr(unsafe.Pointer(&buffer[0])), 109 | uintptr(BUFFER_SIZE), 110 | uintptr(unsafe.Pointer(&bytesRead)), // 传递 bytesRead 的地址,以获取实际读取的字节数 111 | uintptr(unsafe.Pointer(&minByteNeeded)), // 不需要返回的记录数 112 | ) 113 | if ret == 0 { 114 | if err == ERROR_HANDLE_EOF { 115 | break 116 | } else if err == ERROR_INSUFFICIENT_BUFFER { 117 | buffer = make([]byte, minByteNeeded) 118 | BUFFER_SIZE = int(minByteNeeded) 119 | continue 120 | } else { 121 | return nil, err 122 | } 123 | } 124 | return buffer[:bytesRead], nil 125 | } 126 | return nil, errors.New("unable to read event buffer") 127 | } 128 | 129 | func createEvent( 130 | eventAttributes *syscall.SecurityAttributes, 131 | manualReset, initialState uint32, 132 | name *uint16, 133 | ) (syscall.Handle, error) { 134 | return CreateEvent(eventAttributes, manualReset, initialState, name) 135 | } 136 | 137 | func CreateEvent( 138 | eventAttributes *syscall.SecurityAttributes, 139 | manualReset, initialState uint32, 140 | name *uint16, 141 | ) (syscall.Handle, error) { 142 | ret, _, err := procCreateEvent.Call( 143 | uintptr(unsafe.Pointer(eventAttributes)), 144 | uintptr(manualReset), 145 | uintptr(initialState), 146 | uintptr(unsafe.Pointer(name)), 147 | ) 148 | if ret == 0 { 149 | return InvalidHandle, errors.New("unable to create event: " + err.Error()) 150 | } 151 | return syscall.Handle(ret), nil 152 | } 153 | func resetEvent(handle syscall.Handle) error { 154 | return ResetEvent(handle) 155 | } 156 | func ResetEvent(handle syscall.Handle) error { 157 | ret, _, err := procResetEvent.Call( 158 | uintptr(handle), 159 | ) 160 | if ret == 0 { 161 | return errors.New("unable to reset event: " + err.Error()) 162 | } 163 | return nil 164 | } 165 | 166 | func waitForMultipleObjects( 167 | handles []syscall.Handle, 168 | waitAll bool, 169 | waitMilliseconds uint32) (uint32, error) { 170 | return WaitForMultipleObjects(handles, waitAll, waitMilliseconds) 171 | } 172 | 173 | func WaitForMultipleObjects( 174 | handles []syscall.Handle, 175 | waitAll bool, 176 | waitMilliseconds uint32, 177 | ) (event uint32, err error) { 178 | var ptr *syscall.Handle 179 | if len(handles) > 0 { 180 | ptr = &handles[0] 181 | } 182 | ret, _, e1 := procWaitForMultipleObjects.Call( 183 | uintptr(len(handles)), 184 | uintptr(unsafe.Pointer(ptr)), 185 | uintptr(*(*int32)(unsafe.Pointer(&waitAll))), 186 | uintptr(waitMilliseconds), 187 | ) 188 | event = uint32(ret) 189 | if event == syscall.WAIT_FAILED { 190 | err = errors.New("unable to wait for multiple objects: " + e1.Error()) 191 | return 192 | } 193 | return 194 | } 195 | func setEvent(handle syscall.Handle) error { 196 | return SetEvent(handle) 197 | } 198 | func SetEvent(handle syscall.Handle) error { 199 | ret, _, err := procSetEvent.Call( 200 | uintptr(handle), 201 | ) 202 | if ret == 0 { 203 | return errors.New("unable to set event: " + err.Error()) 204 | } 205 | return nil 206 | } 207 | func closeHandle(handle syscall.Handle) error { 208 | return CloseHandle(handle) 209 | } 210 | 211 | func CloseHandle(handle syscall.Handle) error { 212 | ret, _, err := procCloseHandle.Call( 213 | uintptr(handle), 214 | ) 215 | if ret == 0 { 216 | return errors.New("unable to close handle: " + err.Error()) 217 | } 218 | return nil 219 | } 220 | 221 | func registerEventSource(uncServerName, sourceName *uint16) (handle syscall.Handle, err error) { 222 | return RegisterEventSource(uncServerName, sourceName) 223 | } 224 | 225 | func RegisterEventSource(uncServerName, sourceName *uint16) (handle syscall.Handle, err error) { 226 | r0, _, e1 := procRegisterEventSourceW.Call( 227 | uintptr(unsafe.Pointer(uncServerName)), 228 | uintptr(unsafe.Pointer(sourceName)), 229 | ) 230 | handle = syscall.Handle(r0) 231 | if handle == 0 { 232 | err = e1 233 | } 234 | return 235 | } 236 | 237 | func reportEvent(log syscall.Handle, etype uint16, category uint16, eventID uint32, userSid *windows.SID, strings []string, binaryData []byte) error { 238 | return ReportEvent(log, etype, category, eventID, userSid, strings, binaryData) 239 | } 240 | 241 | func ReportEvent(log syscall.Handle, etype uint16, category uint16, eventID uint32, userSid *windows.SID, strings []string, binaryData []byte) error { 242 | var pstrings **uint16 243 | if len(strings) > 0 { 244 | var strs []uintptr 245 | for _, s := range strings { 246 | strs = append(strs, uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(s)))) 247 | } 248 | pstrings = &(*[1 << 30]*uint16)(unsafe.Pointer(&strs[0]))[0] 249 | } 250 | var binaryDataPtr *byte 251 | if len(binaryData) > 0 { 252 | binaryDataPtr = &binaryData[0] 253 | } 254 | r1, _, e1 := procReportEventW.Call( 255 | uintptr(log), 256 | uintptr(etype), 257 | uintptr(category), 258 | uintptr(eventID), 259 | uintptr(unsafe.Pointer(userSid)), 260 | uintptr(len(strings)), 261 | uintptr(len(binaryData)), 262 | uintptr(unsafe.Pointer(pstrings)), 263 | uintptr(unsafe.Pointer(binaryDataPtr)), 264 | ) 265 | if r1 == 0 { 266 | return e1 267 | } 268 | return nil 269 | } 270 | func deregisterEventSource(log syscall.Handle) error { 271 | return DeregisterEventSource(log) 272 | } 273 | 274 | func DeregisterEventSource(log syscall.Handle) error { 275 | r1, _, e1 := procDeregisterEventSource.Call( 276 | uintptr(log), 277 | ) 278 | if r1 == 0 { 279 | return e1 280 | } 281 | return nil 282 | } 283 | --------------------------------------------------------------------------------