├── LICENSE ├── README.md ├── bookmark.c ├── bookmark.go ├── bookmark.h ├── bookmark_test.go ├── event.c ├── event.go ├── event.h ├── event_test.go ├── example └── main.go ├── structs.go ├── winlog.go └── winlog_test.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Rocana 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gowinlog 2 | Go library for subscribing to the Windows Event Log. 3 | 4 | Installation 5 | ======= 6 | 7 | gowinlog uses cgo, so it needs gcc, and `evt.h` must be available. Installing [MinGW-w64](http://mingw-w64.yaxm.org/doku.php) should satisfy both requirements. Make sure the Go architecture and GCC architecture are the same. 8 | 9 | Usage 10 | ======= 11 | 12 | In Go, create a watcher and subscribe to some log channels. Subscriptions can start at the beginning of the log, at the most recent event, or at a bookmark. Events and errors are coerced into Go structs and published on the `Event()` and `Error()` channels. Every event includes a `Bookmark` field which can be stored and used to resume processing at the same point. 13 | 14 | ``` Go 15 | package main 16 | 17 | import ( 18 | "fmt" 19 | "github.com/scalingdata/gowinlog" 20 | ) 21 | 22 | func main() { 23 | watcher, err := winlog.NewWinLogWatcher() 24 | if err != nil { 25 | fmt.Printf("Couldn't create watcher: %v\n", err) 26 | return 27 | } 28 | // Recieve any future messages 29 | watcher.SubscribeFromNow("Application") 30 | for { 31 | select { 32 | case evt := <- watcher.Event(): 33 | // Print the event struct 34 | fmt.Printf("Event: %v\n", evt) 35 | // Print the updated bookmark for that channel 36 | fmt.Printf("Bookmark XML: %v\n", evt.Bookmark) 37 | case err := <- watcher.Error(): 38 | fmt.Printf("Error: %v\n\n", err) 39 | } 40 | } 41 | } 42 | ``` 43 | 44 | Low-level API 45 | ------ 46 | 47 | `event.go` contains wrappers around the C events API. `bookmark.go` has wrappers around the bookmark API. 48 | -------------------------------------------------------------------------------- /bookmark.c: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | #include "bookmark.h" 4 | 5 | ULONGLONG CreateBookmark() { 6 | return (ULONGLONG)EvtCreateBookmark(NULL); 7 | } 8 | 9 | ULONGLONG CreateBookmarkFromXML(char* xmlString) { 10 | size_t xmlWideLen= mbstowcs(NULL, xmlString, 0) + 1; 11 | LPWSTR lxmlString = malloc(xmlWideLen * sizeof(wchar_t)); 12 | if (!lxmlString) { 13 | SetLastError(ERROR_NOT_ENOUGH_MEMORY); 14 | return 0; 15 | } 16 | 17 | // Convert Go string to wide characters 18 | mbstowcs(lxmlString, xmlString, xmlWideLen); 19 | return (ULONGLONG)EvtCreateBookmark(lxmlString); 20 | } 21 | 22 | int UpdateBookmark(ULONGLONG hBookmark, ULONGLONG hEvent) { 23 | return EvtUpdateBookmark((EVT_HANDLE)hBookmark, (EVT_HANDLE)hEvent); 24 | } 25 | 26 | char* RenderBookmark(ULONGLONG hBookmark) { 27 | DWORD dwUsed; 28 | DWORD dwProps; 29 | DWORD dwSize = 0; 30 | EvtRender(NULL, (EVT_HANDLE)hBookmark, EvtRenderBookmark, dwSize, NULL, &dwUsed, &dwProps); 31 | if (GetLastError() != ERROR_INSUFFICIENT_BUFFER){ 32 | return NULL; 33 | } 34 | dwSize = dwUsed + 1; 35 | LPWSTR xmlWide = malloc((dwSize) * sizeof(wchar_t)); 36 | if (!xmlWide) { 37 | SetLastError(ERROR_NOT_ENOUGH_MEMORY); 38 | return NULL; 39 | } 40 | int renderResult = EvtRender(NULL, (EVT_HANDLE)hBookmark, EvtRenderBookmark, dwSize, xmlWide, &dwUsed, &dwProps); 41 | if (!renderResult) { 42 | free(xmlWide); 43 | return 0; 44 | } 45 | size_t xmlNarrowLen = wcstombs(NULL, xmlWide, 0) + 1; 46 | void* xmlNarrow = malloc(xmlNarrowLen); 47 | if (!xmlNarrow) { 48 | free(xmlWide); 49 | SetLastError(ERROR_NOT_ENOUGH_MEMORY); 50 | return NULL; 51 | } 52 | wcstombs(xmlNarrow, xmlWide, xmlNarrowLen); 53 | free(xmlWide); 54 | return xmlNarrow; 55 | } 56 | -------------------------------------------------------------------------------- /bookmark.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package winlog 4 | 5 | /* 6 | #cgo LDFLAGS: -l wevtapi 7 | #include "bookmark.h" 8 | */ 9 | import "C" 10 | import ( 11 | "unsafe" 12 | ) 13 | 14 | func CreateBookmark() (BookmarkHandle, error) { 15 | bookmark := BookmarkHandle(C.CreateBookmark()) 16 | if bookmark == 0 { 17 | return 0, GetLastError() 18 | } 19 | return bookmark, nil 20 | } 21 | 22 | func CreateBookmarkFromXml(xmlString string) (BookmarkHandle, error) { 23 | cString := C.CString(xmlString) 24 | bookmark := C.CreateBookmarkFromXML(cString) 25 | C.free(unsafe.Pointer(cString)) 26 | if bookmark == 0 { 27 | return 0, GetLastError() 28 | } 29 | return BookmarkHandle(bookmark), nil 30 | } 31 | 32 | func UpdateBookmark(bookmarkHandle BookmarkHandle, eventHandle EventHandle) error { 33 | if C.UpdateBookmark(C.ULONGLONG(bookmarkHandle), C.ULONGLONG(eventHandle)) == 0 { 34 | return GetLastError() 35 | } 36 | return nil 37 | } 38 | 39 | func RenderBookmark(bookmarkHandle BookmarkHandle) (string, error) { 40 | cString := C.RenderBookmark(C.ULONGLONG(bookmarkHandle)) 41 | if cString == nil { 42 | return "", GetLastError() 43 | } 44 | bookmarkXml := C.GoString(cString) 45 | C.free(unsafe.Pointer(cString)) 46 | return bookmarkXml, nil 47 | } 48 | -------------------------------------------------------------------------------- /bookmark.h: -------------------------------------------------------------------------------- 1 | #define _WIN32_WINNT 0x0602 2 | 3 | #include 4 | #include "winevt.h" 5 | #include 6 | #include 7 | #include 8 | 9 | // Make a new, empty bookmark to update 10 | ULONGLONG CreateBookmark(); 11 | // Load an existing bookmark from a Go XML string 12 | ULONGLONG CreateBookmarkFromXML(char* xmlString); 13 | // Update a bookmark to the given event handle 14 | int UpdateBookmark(ULONGLONG hBookmark, ULONGLONG hEvent); 15 | // Render the XML string for a bookmark 16 | char* RenderBookmark(ULONGLONG hBookmark); 17 | -------------------------------------------------------------------------------- /bookmark_test.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package winlog 4 | 5 | import ( 6 | "encoding/xml" 7 | . "testing" 8 | "unsafe" 9 | ) 10 | 11 | type bookmarkListXml struct { 12 | XMLName xml.Name `xml:"BookmarkList"` 13 | Bookmarks []bookmarkXml `xml:"Bookmark"` 14 | } 15 | 16 | type bookmarkXml struct { 17 | XMLName xml.Name `xml:"Bookmark"` 18 | RecordId uint64 `xml:"RecordId,attr"` 19 | Channel string `xml:"Channel,attr"` 20 | } 21 | 22 | func TestSerializeBookmark(t *T) { 23 | testBookmarkXml := "\r\n \r\n" 24 | bookmark, err := CreateBookmarkFromXml(testBookmarkXml) 25 | if err != nil { 26 | t.Fatal(err) 27 | } 28 | defer CloseEventHandle(uint64(bookmark)) 29 | xmlString, err := RenderBookmark(bookmark) 30 | if err != nil { 31 | t.Fatal(err) 32 | } 33 | if xmlString != testBookmarkXml { 34 | t.Fatalf("Serialized bookmark not equal to original %q != %q", testBookmarkXml, xmlString) 35 | } 36 | } 37 | 38 | func TestCreateInvalidXml(t *T) { 39 | testBookmarkXml := "\r\n " 40 | bookmark, err := CreateBookmarkFromXml(testBookmarkXml) 41 | if err == nil { 42 | t.Fatal("No error from invalid bookmark XML") 43 | } 44 | if bookmark != 0 { 45 | t.Fatal("Got handle from invalid bookmark XML") 46 | } 47 | } 48 | 49 | func TestUpdateBookmark(t *T) { 50 | // Create a bookmark and update it with an event 51 | bookmark, err := CreateBookmark() 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | defer CloseEventHandle(uint64(bookmark)) 56 | event, err := getTestEventHandle() 57 | if err != nil { 58 | t.Fatal(err) 59 | } 60 | err = UpdateBookmark(bookmark, event) 61 | if err != nil { 62 | t.Fatal(err) 63 | } 64 | 65 | // Decode the XML bookmark 66 | xmlString, err := RenderBookmark(bookmark) 67 | if err != nil { 68 | t.Fatal(err) 69 | } 70 | var bookmarkStruct bookmarkListXml 71 | if err := xml.Unmarshal([]byte(xmlString), &bookmarkStruct); err != nil { 72 | t.Fatal(err) 73 | } 74 | if len(bookmarkStruct.Bookmarks) != 1 { 75 | t.Fatalf("Got %v bookmarks, expected 1", len(bookmarkStruct.Bookmarks)) 76 | } 77 | 78 | // Extract the corresponding Event properties 79 | renderContext, err := GetSystemRenderContext() 80 | if err != nil { 81 | t.Fatal(err) 82 | } 83 | defer CloseEventHandle(uint64(renderContext)) 84 | renderedFields, err := RenderEventValues(renderContext, event) 85 | if err != nil { 86 | t.Fatal(err) 87 | } 88 | defer Free(unsafe.Pointer(renderedFields)) 89 | channel, _ := RenderStringField(renderedFields, EvtSystemChannel) 90 | eventId, _ := RenderUIntField(renderedFields, EvtSystemEventRecordId) 91 | bookmarkChannel := bookmarkStruct.Bookmarks[0].Channel 92 | bookmarkId := bookmarkStruct.Bookmarks[0].RecordId 93 | 94 | // Check bookmark channel and record ID match 95 | if channel != bookmarkChannel { 96 | t.Fatalf("Bookmark channel %v not equal to event channel %v", bookmarkChannel, channel) 97 | } 98 | if bookmarkId != eventId { 99 | t.Fatalf("Bookmark recordId %v not equal to event id %v", bookmarkId, eventId) 100 | } 101 | } 102 | 103 | func TestUpdateInvalidBookmark(t *T) { 104 | // Create a bookmark and update it with a NULL event 105 | bookmark, err := CreateBookmark() 106 | if err != nil { 107 | t.Fatal(err) 108 | } 109 | err = UpdateBookmark(bookmark, 0) 110 | if err == nil { 111 | t.Fatal("No error when updating bookmark with invalid handle") 112 | } 113 | } 114 | 115 | func BenchmarkBookmark(b *B) { 116 | // Create a bookmark and update it with an event 117 | bookmark, err := CreateBookmark() 118 | if err != nil { 119 | b.Fatal(err) 120 | } 121 | defer CloseEventHandle(uint64(bookmark)) 122 | for i := 0; i < b.N; i++ { 123 | event, err := getTestEventHandle() 124 | if err != nil { 125 | b.Fatal(err) 126 | } 127 | err = UpdateBookmark(bookmark, event) 128 | if err != nil { 129 | b.Fatal(err) 130 | } 131 | _, err = RenderBookmark(bookmark) 132 | if err != nil { 133 | b.Fatal(err) 134 | } 135 | CloseEventHandle(uint64(event)) 136 | } 137 | } 138 | 139 | func BenchmarkNoBookmark(b *B) { 140 | // Create a bookmark and update it with an event 141 | bookmark, err := CreateBookmark() 142 | if err != nil { 143 | b.Fatal(err) 144 | } 145 | defer CloseEventHandle(uint64(bookmark)) 146 | for i := 0; i < b.N; i++ { 147 | event, err := getTestEventHandle() 148 | if err != nil { 149 | b.Fatal(err) 150 | } 151 | err = UpdateBookmark(bookmark, event) 152 | if err != nil { 153 | b.Fatal(err) 154 | } 155 | CloseEventHandle(uint64(event)) 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /event.c: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | // This file contains convenience stub functions that call Win32 API 4 | // functions and return values suitable for handling in Go. 5 | // Note that some functions such as EvtRender() and EvtFormatMessage() support a model 6 | // where they are called twice - once to determine the necessary buffer size, 7 | // and once to copy values into the supplied buffer. 8 | 9 | #define _WIN32_WINNT 0x0602 10 | 11 | #include "event.h" 12 | #include "_cgo_export.h" 13 | 14 | int CloseEvtHandle(ULONGLONG hEvent) { 15 | EvtClose((EVT_HANDLE)hEvent); 16 | } 17 | 18 | int CancelEvtHandle(ULONGLONG hEvent) { 19 | EvtCancel((EVT_HANDLE)hEvent); 20 | } 21 | 22 | PVOID RenderEventValues(ULONGLONG hContext, ULONGLONG hEvent) { 23 | DWORD dwBufferSize = 0; 24 | DWORD dwUsed = 0; 25 | DWORD dwPropertyCount = 0; 26 | EvtRender((EVT_HANDLE)hContext, (EVT_HANDLE)hEvent, EvtRenderEventValues, dwBufferSize, NULL, &dwUsed, &dwPropertyCount); 27 | PVOID pRenderedValues = malloc(dwUsed); 28 | if (!pRenderedValues) { 29 | SetLastError(ERROR_NOT_ENOUGH_MEMORY); 30 | return NULL; 31 | } 32 | dwBufferSize = dwUsed; 33 | if (! EvtRender((EVT_HANDLE)hContext, (EVT_HANDLE)hEvent, EvtRenderEventValues, dwBufferSize, pRenderedValues, &dwUsed, &dwPropertyCount)){ 34 | free(pRenderedValues); 35 | return NULL; 36 | } 37 | return pRenderedValues; 38 | } 39 | 40 | // Render the event as XML. The returned buffer must be freed after use. 41 | char* RenderEventXML(ULONGLONG hEvent) { 42 | DWORD dwBufferSize = 0; 43 | DWORD dwUsed = 0; 44 | DWORD dwPropertyCount = 0; 45 | EvtRender(NULL, (EVT_HANDLE)hEvent, EvtRenderEventXml, dwBufferSize, NULL, &dwUsed, &dwPropertyCount); 46 | 47 | // Allocate a buffer to hold the utf-16 encoded xml string. Although the xml 48 | // string is utf-16, the dwUsed value is in bytes, not characters 49 | // See https://msdn.microsoft.com/en-us/library/windows/desktop/aa385471(v=vs.85).aspx 50 | LPWSTR xmlWide = malloc(dwUsed); 51 | if (!xmlWide) { 52 | SetLastError(ERROR_NOT_ENOUGH_MEMORY); 53 | return NULL; 54 | } 55 | dwBufferSize = dwUsed; 56 | if (! EvtRender(NULL, (EVT_HANDLE)hEvent, EvtRenderEventXml, dwBufferSize, xmlWide, &dwUsed, 0)){ 57 | free(xmlWide); 58 | return NULL; 59 | } 60 | 61 | // Convert the xml string to multibyte 62 | size_t lenXml = wcstombs(NULL, xmlWide, 0) + 1; 63 | void* xml = malloc(lenXml); 64 | if (!xml) { 65 | free(xmlWide); 66 | SetLastError(ERROR_NOT_ENOUGH_MEMORY); 67 | return NULL; 68 | } 69 | wcstombs(xml, xmlWide, lenXml); 70 | free(xmlWide); 71 | return xml; 72 | } 73 | 74 | char* GetLastErrorString() { 75 | DWORD dwErr = GetLastError(); 76 | LPSTR lpszMsgBuf; 77 | FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS, 0, dwErr, 0, (LPSTR)&lpszMsgBuf, 0, NULL); 78 | return (char *)lpszMsgBuf; 79 | } 80 | 81 | char* GetFormattedMessage(ULONGLONG hEventPublisher, ULONGLONG hEvent, int format) { 82 | DWORD dwBufferSize = 0; 83 | DWORD dwBufferUsed = 0; 84 | int status; 85 | errno_t decodeReturn = EvtFormatMessage((EVT_HANDLE)hEventPublisher, (EVT_HANDLE)hEvent, 0, 0, NULL, format, 0, NULL, &dwBufferUsed); 86 | if ((status = GetLastError()) != ERROR_INSUFFICIENT_BUFFER) { 87 | return NULL; 88 | } 89 | dwBufferSize = dwBufferUsed + 1; 90 | LPWSTR messageWide = malloc((dwBufferSize) * sizeof(wchar_t)); 91 | if (!messageWide) { 92 | SetLastError(ERROR_NOT_ENOUGH_MEMORY); 93 | return NULL; 94 | } 95 | decodeReturn = EvtFormatMessage((EVT_HANDLE)hEventPublisher, (EVT_HANDLE)hEvent, 0, 0, NULL, format, dwBufferSize, messageWide, &dwBufferUsed); 96 | if (!decodeReturn) { 97 | free(messageWide); 98 | return NULL; 99 | } 100 | size_t lenMessage = wcstombs(NULL, messageWide, 0) + 1; 101 | void* message = malloc(lenMessage); 102 | if (!message) { 103 | free(messageWide); 104 | SetLastError(ERROR_NOT_ENOUGH_MEMORY); 105 | return NULL; 106 | } 107 | wcstombs(message, messageWide, lenMessage); 108 | free(messageWide); 109 | return message; 110 | } 111 | 112 | ULONGLONG GetEventPublisherHandle(PVOID pRenderedValues) { 113 | LPCWSTR publisher = ((PEVT_VARIANT)pRenderedValues)[EvtSystemProviderName].StringVal; 114 | return (ULONGLONG)EvtOpenPublisherMetadata(NULL, publisher, NULL, 0, 0); 115 | } 116 | 117 | ULONGLONG CreateSystemRenderContext() { 118 | return (ULONGLONG)EvtCreateRenderContext(0, NULL, EvtRenderContextSystem); 119 | } 120 | 121 | int GetRenderedValueType(PVOID pRenderedValues, int property) { 122 | return (int)((PEVT_VARIANT)pRenderedValues)[property].Type; 123 | } 124 | 125 | char* GetRenderedStringValue(PVOID pRenderedValues, int property) { 126 | wchar_t const * propVal = ((PEVT_VARIANT)pRenderedValues)[property].StringVal; 127 | size_t lenNarrowPropVal = wcstombs(NULL, propVal, 0) + 1; 128 | char* value = malloc(lenNarrowPropVal); 129 | if (!value) { 130 | SetLastError(ERROR_NOT_ENOUGH_MEMORY); 131 | return NULL; 132 | } 133 | wcstombs(value, propVal, lenNarrowPropVal); 134 | return value; 135 | } 136 | 137 | ULONGLONG GetRenderedByteValue(PVOID pRenderedValues, int property) { 138 | return ((PEVT_VARIANT)pRenderedValues)[property].ByteVal; 139 | } 140 | 141 | ULONGLONG GetRenderedUInt16Value(PVOID pRenderedValues, int property) { 142 | return ((PEVT_VARIANT)pRenderedValues)[property].UInt16Val; 143 | } 144 | 145 | ULONGLONG GetRenderedUInt32Value(PVOID pRenderedValues, int property) { 146 | return ((PEVT_VARIANT)pRenderedValues)[property].UInt32Val; 147 | } 148 | 149 | ULONGLONG GetRenderedUInt64Value(PVOID pRenderedValues, int property) { 150 | return ((PEVT_VARIANT)pRenderedValues)[property].UInt64Val; 151 | } 152 | 153 | LONGLONG GetRenderedSByteValue(PVOID pRenderedValues, int property) { 154 | return ((PEVT_VARIANT)pRenderedValues)[property].SByteVal; 155 | } 156 | 157 | LONGLONG GetRenderedInt16Value(PVOID pRenderedValues, int property) { 158 | return ((PEVT_VARIANT)pRenderedValues)[property].Int16Val; 159 | } 160 | 161 | LONGLONG GetRenderedInt32Value(PVOID pRenderedValues, int property) { 162 | return ((PEVT_VARIANT)pRenderedValues)[property].Int32Val; 163 | } 164 | 165 | LONGLONG GetRenderedInt64Value(PVOID pRenderedValues, int property) { 166 | return ((PEVT_VARIANT)pRenderedValues)[property].Int64Val; 167 | } 168 | 169 | //FILETIME to unix epoch: https://support.microsoft.com/en-us/kb/167296 170 | ULONGLONG GetRenderedFileTimeValue(PVOID pRenderedValues, int property) { 171 | FILETIME* ft = (FILETIME*) &(((PEVT_VARIANT)pRenderedValues)[property].FileTimeVal); 172 | ULONGLONG time = ft->dwHighDateTime; 173 | time = (time << 32) | ft->dwLowDateTime; 174 | return (time / 10000000) - 11644473600; 175 | } 176 | 177 | // Dispatch events and errors appropriately 178 | DWORD WINAPI SubscriptionCallback(EVT_SUBSCRIBE_NOTIFY_ACTION action, PVOID pContext, EVT_HANDLE hEvent) 179 | { 180 | switch(action) 181 | { 182 | case EvtSubscribeActionError: 183 | // In this case, hEvent is an error code, not a handle to an event 184 | // https://msdn.microsoft.com/en-us/library/windows/desktop/aa385596(v=vs.85).aspx 185 | eventCallbackError((ULONGLONG)hEvent, pContext); 186 | break; 187 | 188 | case EvtSubscribeActionDeliver: 189 | eventCallback((ULONGLONG)hEvent, pContext); 190 | break; 191 | 192 | default: 193 | // TODO: signal unknown error 194 | eventCallbackError(0, pContext); 195 | } 196 | 197 | return ERROR_SUCCESS; // The service ignores the returned status. 198 | } 199 | 200 | ULONGLONG SetupListener(char* channel, char* query, PVOID pWatcher, EVT_HANDLE hBookmark, EVT_SUBSCRIBE_FLAGS flags) 201 | { 202 | DWORD status = ERROR_SUCCESS; 203 | EVT_HANDLE hSubscription = NULL; 204 | size_t wideChannelLen; 205 | size_t maxWideChannelLen = mbstowcs(NULL, channel, 0) + 1; 206 | LPWSTR lChannel = malloc(maxWideChannelLen * sizeof(wchar_t)); 207 | if (!lChannel) { 208 | SetLastError(ERROR_NOT_ENOUGH_MEMORY); 209 | return 0; 210 | } 211 | 212 | size_t maxWideQueryLen = mbstowcs(NULL, query, 0) + 1; 213 | LPWSTR lQuery = malloc(maxWideQueryLen * sizeof(wchar_t)); 214 | if (!lQuery) { 215 | SetLastError(ERROR_NOT_ENOUGH_MEMORY); 216 | return 0; 217 | } 218 | 219 | // Convert Go string to wide characters 220 | mbstowcs(lChannel, channel, maxWideChannelLen); 221 | mbstowcs(lQuery, query, maxWideQueryLen); 222 | 223 | // Subscribe to events beginning in the present. All future events will trigger the callback. 224 | hSubscription = EvtSubscribe(NULL, NULL, lChannel, lQuery, hBookmark, pWatcher, (EVT_SUBSCRIBE_CALLBACK)SubscriptionCallback, flags); 225 | free(lChannel); 226 | return (ULONGLONG)hSubscription; 227 | } 228 | 229 | ULONGLONG CreateListener(char* channel, char* query, int startPos, PVOID pWatcher) { 230 | return SetupListener(channel, query, pWatcher, NULL, startPos); 231 | } 232 | 233 | ULONGLONG CreateListenerFromBookmark(char* channel, char* query, PVOID pWatcher, ULONGLONG hBookmark) { 234 | return SetupListener(channel, query, pWatcher, (EVT_HANDLE)hBookmark, EvtSubscribeStartAfterBookmark); 235 | } 236 | 237 | ULONGLONG GetTestEventHandle() { 238 | DWORD status = ERROR_SUCCESS; 239 | EVT_HANDLE record = 0; 240 | DWORD recordsReturned; 241 | EVT_HANDLE result = EvtQuery(NULL, L"Application", L"*", EvtQueryChannelPath); 242 | if (result == 0) { 243 | return 0; 244 | } 245 | if (!EvtNext(result, 1, &record, 500, 0, &recordsReturned)) { 246 | EvtClose(result); 247 | return 0; 248 | } 249 | EvtClose(result); 250 | return (ULONGLONG)record; 251 | } 252 | -------------------------------------------------------------------------------- /event.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package winlog 4 | 5 | /* 6 | #cgo LDFLAGS: -l wevtapi 7 | #include "event.h" 8 | */ 9 | import "C" 10 | import ( 11 | "errors" 12 | "fmt" 13 | "time" 14 | "unsafe" 15 | ) 16 | 17 | type EVT_SUBSCRIBE_FLAGS int 18 | 19 | const ( 20 | _ = iota 21 | EvtSubscribeToFutureEvents 22 | EvtSubscribeStartAtOldestRecord 23 | EvtSubscribeStartAfterBookmark 24 | ) 25 | 26 | type EVT_VARIANT_TYPE int 27 | 28 | const ( 29 | EvtVarTypeNull = iota 30 | EvtVarTypeString 31 | EvtVarTypeAnsiString 32 | EvtVarTypeSByte 33 | EvtVarTypeByte 34 | EvtVarTypeInt16 35 | EvtVarTypeUInt16 36 | EvtVarTypeInt32 37 | EvtVarTypeUInt32 38 | EvtVarTypeInt64 39 | EvtVarTypeUInt64 40 | EvtVarTypeSingle 41 | EvtVarTypeDouble 42 | EvtVarTypeBoolean 43 | EvtVarTypeBinary 44 | EvtVarTypeGuid 45 | EvtVarTypeSizeT 46 | EvtVarTypeFileTime 47 | EvtVarTypeSysTime 48 | EvtVarTypeSid 49 | EvtVarTypeHexInt32 50 | EvtVarTypeHexInt64 51 | EvtVarTypeEvtHandle 52 | EvtVarTypeEvtXml 53 | ) 54 | 55 | /* Fields that can be rendered with GetRendered*Value */ 56 | type EVT_SYSTEM_PROPERTY_ID int 57 | 58 | const ( 59 | EvtSystemProviderName = iota 60 | EvtSystemProviderGuid 61 | EvtSystemEventID 62 | EvtSystemQualifiers 63 | EvtSystemLevel 64 | EvtSystemTask 65 | EvtSystemOpcode 66 | EvtSystemKeywords 67 | EvtSystemTimeCreated 68 | EvtSystemEventRecordId 69 | EvtSystemActivityID 70 | EvtSystemRelatedActivityID 71 | EvtSystemProcessID 72 | EvtSystemThreadID 73 | EvtSystemChannel 74 | EvtSystemComputer 75 | EvtSystemUserID 76 | EvtSystemVersion 77 | ) 78 | 79 | /* Formatting modes for GetFormattedMessage */ 80 | type EVT_FORMAT_MESSAGE_FLAGS int 81 | 82 | const ( 83 | _ = iota 84 | EvtFormatMessageEvent 85 | EvtFormatMessageLevel 86 | EvtFormatMessageTask 87 | EvtFormatMessageOpcode 88 | EvtFormatMessageKeyword 89 | EvtFormatMessageChannel 90 | EvtFormatMessageProvider 91 | EvtFormatMessageId 92 | EvtFormatMessageXml 93 | ) 94 | 95 | // Get a handle to a render context which will render properties from the System element. 96 | // Wraps EvtCreateRenderContext() with Flags = EvtRenderContextSystem. The resulting 97 | // handle must be closed with CloseEventHandle. 98 | func GetSystemRenderContext() (SysRenderContext, error) { 99 | context := SysRenderContext(C.CreateSystemRenderContext()) 100 | if context == 0 { 101 | return 0, GetLastError() 102 | } 103 | return context, nil 104 | } 105 | 106 | // Get a handle for a event log subscription on the given channel. 107 | // `query` is an XPath expression to filter the events on the channel - "*" allows all events. 108 | // The resulting handle must be closed with CloseEventHandle. 109 | func CreateListener(channel, query string, startpos EVT_SUBSCRIBE_FLAGS, watcher *LogEventCallbackWrapper) (ListenerHandle, error) { 110 | cChan := C.CString(channel) 111 | cQuery := C.CString(query) 112 | listenerHandle := C.CreateListener(cChan, cQuery, C.int(startpos), C.PVOID(watcher)) 113 | C.free(unsafe.Pointer(cChan)) 114 | C.free(unsafe.Pointer(cQuery)) 115 | if listenerHandle == 0 { 116 | return 0, GetLastError() 117 | } 118 | return ListenerHandle(listenerHandle), nil 119 | } 120 | 121 | // Get a handle for an event log subscription on the given channel. Will begin at the 122 | // bookmarked event, or the closest possible event if the log has been truncated. 123 | // `query` is an XPath expression to filter the events on the channel - "*" allows all events. 124 | // The resulting handle must be closed with CloseEventHandle. 125 | func CreateListenerFromBookmark(channel, query string, watcher *LogEventCallbackWrapper, bookmarkHandle BookmarkHandle) (ListenerHandle, error) { 126 | cChan := C.CString(channel) 127 | cQuery := C.CString(query) 128 | listenerHandle := C.CreateListenerFromBookmark(cChan, cQuery, C.PVOID(watcher), C.ULONGLONG(bookmarkHandle)) 129 | C.free(unsafe.Pointer(cChan)) 130 | C.free(unsafe.Pointer(cQuery)) 131 | if listenerHandle == 0 { 132 | return 0, GetLastError() 133 | } 134 | return ListenerHandle(listenerHandle), nil 135 | } 136 | 137 | // Get the Go string for the field at the given index. Returns 138 | // false if the type of the field isn't EvtVarTypeString. 139 | func RenderStringField(fields RenderedFields, fieldIndex EVT_SYSTEM_PROPERTY_ID) (string, bool) { 140 | fieldType := C.GetRenderedValueType(C.PVOID(fields), C.int(fieldIndex)) 141 | if fieldType != EvtVarTypeString { 142 | return "", false 143 | } 144 | 145 | cString := C.GetRenderedStringValue(C.PVOID(fields), C.int(fieldIndex)) 146 | if cString == nil { 147 | return "", false 148 | } 149 | 150 | value := C.GoString(cString) 151 | C.free(unsafe.Pointer(cString)) 152 | return value, true 153 | } 154 | 155 | // Get the timestamp of the field at the given index. Returns false if the 156 | // type of the field isn't EvtVarTypeFileTime. 157 | func RenderFileTimeField(fields RenderedFields, fieldIndex EVT_SYSTEM_PROPERTY_ID) (time.Time, bool) { 158 | fieldType := C.GetRenderedValueType(C.PVOID(fields), C.int(fieldIndex)) 159 | if fieldType != EvtVarTypeFileTime { 160 | return time.Time{}, false 161 | } 162 | field := C.GetRenderedFileTimeValue(C.PVOID(fields), C.int(fieldIndex)) 163 | return time.Unix(int64(field), 0), true 164 | } 165 | 166 | // Get the unsigned integer at the given index. Returns false if the field 167 | // type isn't EvtVarTypeByte, EvtVarTypeUInt16, EvtVarTypeUInt32, or EvtVarTypeUInt64. 168 | func RenderUIntField(fields RenderedFields, fieldIndex EVT_SYSTEM_PROPERTY_ID) (uint64, bool) { 169 | var field C.ULONGLONG 170 | fieldType := C.GetRenderedValueType(C.PVOID(fields), C.int(fieldIndex)) 171 | switch fieldType { 172 | case EvtVarTypeByte: 173 | field = C.GetRenderedByteValue(C.PVOID(fields), C.int(fieldIndex)) 174 | case EvtVarTypeUInt16: 175 | field = C.GetRenderedUInt16Value(C.PVOID(fields), C.int(fieldIndex)) 176 | case EvtVarTypeUInt32: 177 | field = C.GetRenderedUInt32Value(C.PVOID(fields), C.int(fieldIndex)) 178 | case EvtVarTypeUInt64: 179 | field = C.GetRenderedUInt64Value(C.PVOID(fields), C.int(fieldIndex)) 180 | default: 181 | return 0, false 182 | } 183 | 184 | return uint64(field), true 185 | } 186 | 187 | // Get the signed integer at the given index. Returns false if the type of 188 | // the field isn't EvtVarTypeSByte, EvtVarTypeInt16, EvtVarTypeInt32, EvtVarTypeInt64. 189 | func RenderIntField(fields RenderedFields, fieldIndex EVT_SYSTEM_PROPERTY_ID) (int64, bool) { 190 | var field C.LONGLONG 191 | fieldType := C.GetRenderedValueType(C.PVOID(fields), C.int(fieldIndex)) 192 | switch fieldType { 193 | case EvtVarTypeSByte: 194 | field = C.GetRenderedSByteValue(C.PVOID(fields), C.int(fieldIndex)) 195 | case EvtVarTypeInt16: 196 | field = C.GetRenderedInt16Value(C.PVOID(fields), C.int(fieldIndex)) 197 | case EvtVarTypeInt32: 198 | field = C.GetRenderedInt32Value(C.PVOID(fields), C.int(fieldIndex)) 199 | case EvtVarTypeInt64: 200 | field = C.GetRenderedInt64Value(C.PVOID(fields), C.int(fieldIndex)) 201 | default: 202 | return 0, false 203 | } 204 | 205 | return int64(field), true 206 | } 207 | 208 | // Get the formatted string that represents this message. This method wraps EvtFormatMessage. 209 | func FormatMessage(eventPublisherHandle PublisherHandle, eventHandle EventHandle, format EVT_FORMAT_MESSAGE_FLAGS) (string, error) { 210 | cString := C.GetFormattedMessage(C.ULONGLONG(eventPublisherHandle), C.ULONGLONG(eventHandle), C.int(format)) 211 | if cString == nil { 212 | return "", GetLastError() 213 | } 214 | value := C.GoString(cString) 215 | C.free(unsafe.Pointer(cString)) 216 | return value, nil 217 | } 218 | 219 | // Get the formatted string for the last error which occurred. Wraps GetLastError and FormatMessage. 220 | func GetLastError() error { 221 | errStr := C.GetLastErrorString() 222 | err := errors.New(C.GoString(errStr)) 223 | C.LocalFree(C.HLOCAL(errStr)) 224 | return err 225 | } 226 | 227 | // Render the system properties from the event and returns an array of properties. 228 | // Properties can be accessed using RenderStringField, RenderIntField, RenderFileTimeField, 229 | // or RenderUIntField depending on type. This buffer must be freed after use. 230 | func RenderEventValues(renderContext SysRenderContext, eventHandle EventHandle) (RenderedFields, error) { 231 | values := RenderedFields(C.RenderEventValues(C.ULONGLONG(renderContext), C.ULONGLONG(eventHandle))) 232 | if values == nil { 233 | return nil, GetLastError() 234 | } 235 | return values, nil 236 | } 237 | 238 | // Render the event as XML. 239 | func RenderEventXML(eventHandle EventHandle) (string, error) { 240 | xml := C.RenderEventXML(C.ULONGLONG(eventHandle)) 241 | if xml == nil { 242 | return "", GetLastError() 243 | } 244 | xmlString := C.GoString(xml) 245 | C.free(unsafe.Pointer(xml)) 246 | return xmlString, nil 247 | } 248 | 249 | // Get a handle that represents the publisher of the event, given the rendered event values. 250 | func GetEventPublisherHandle(renderedFields RenderedFields) (PublisherHandle, error) { 251 | handle := PublisherHandle(C.GetEventPublisherHandle(C.PVOID(renderedFields))) 252 | if handle == 0 { 253 | return 0, GetLastError() 254 | } 255 | return handle, nil 256 | } 257 | 258 | // Close an event handle. 259 | func CloseEventHandle(handle uint64) error { 260 | if C.CloseEvtHandle(C.ULONGLONG(handle)) != 1 { 261 | return GetLastError() 262 | } 263 | return nil 264 | } 265 | 266 | // Cancel pending actions on the event handle. 267 | func CancelEventHandle(handle uint64) error { 268 | if C.CancelEvtHandle(C.ULONGLONG(handle)) != 1 { 269 | return GetLastError() 270 | } 271 | return nil 272 | } 273 | 274 | func Free(ptr unsafe.Pointer) { 275 | C.free(ptr) 276 | } 277 | 278 | /* Get the first event in the log, for testing */ 279 | func getTestEventHandle() (EventHandle, error) { 280 | handle := C.GetTestEventHandle() 281 | if handle == 0 { 282 | return 0, GetLastError() 283 | } 284 | return EventHandle(handle), nil 285 | } 286 | 287 | /* These are entry points for the callback to hand the pointer to Go-land. 288 | Note: handles are only valid within the callback. Don't pass them out. */ 289 | 290 | //export eventCallbackError 291 | func eventCallbackError(errCode C.ULONGLONG, logWatcher unsafe.Pointer) { 292 | watcher := (*LogEventCallbackWrapper)(logWatcher).callback 293 | // The provided errCode can be looked up in the Microsoft System Error Code table: 294 | // https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx 295 | watcher.PublishError(fmt.Errorf("Event log callback got error code: %v", errCode)) 296 | } 297 | 298 | //export eventCallback 299 | func eventCallback(handle C.ULONGLONG, logWatcher unsafe.Pointer) { 300 | wrapper := (*LogEventCallbackWrapper)(logWatcher) 301 | watcher := wrapper.callback 302 | watcher.PublishEvent(EventHandle(handle), wrapper.subscribedChannel) 303 | } 304 | -------------------------------------------------------------------------------- /event.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include "winevt.h" 3 | #include 4 | #include 5 | #include 6 | 7 | /* Handles really should be EVT_HANDLE, but Go will sometimes 8 | try to check if pointers are valid, and handles aren't necessarily 9 | pointers (although they have type PVOID). So we pass handles to Go as 10 | 64-bit unsigned ints. */ 11 | 12 | // Create a new listener on the given channel. Events will be passed 13 | // to the callback of *pWatcher. Starts at the current position in the log 14 | ULONGLONG CreateListener(char* channel, char* query, int startpos, PVOID pWatcher); 15 | 16 | // Create a new listener on the given channel. Events will be passed 17 | // to the callback of *pWatcher. Starts at the given bookmark handle. 18 | // Note: This doesn't set the strict flag - if the log was truncated between 19 | // the bookmark and now, it'll continue silently from the earliest event. 20 | ULONGLONG CreateListenerFromBookmark(char* channel, char* query, PVOID pWatcher, ULONGLONG hBookmark); 21 | 22 | // Get the string for the last error code 23 | char* GetLastErrorString(); 24 | 25 | // Render the fields for the given context. Allocates an array 26 | // of values based on the context, these can be accessed using 27 | // GetRenderedValue. Buffer must be freed by the caller. 28 | PVOID RenderEventValues(ULONGLONG hContext, ULONGLONG hEvent); 29 | 30 | // Render the event's XML body 31 | char* RenderEventXML(ULONGLONG hEvent); 32 | 33 | 34 | // Get the type of the variable at the given index in the array. 35 | // Possible types are EvtVarType* 36 | int GetRenderedValueType(PVOID pRenderedValues, int property); 37 | 38 | // Get the value of the variable at the given index. You must know 39 | // the type or this will go badly. 40 | LONGLONG GetRenderedSByteValue(PVOID pRenderedValues, int property); 41 | LONGLONG GetRenderedInt16Value(PVOID pRenderedValues, int property); 42 | LONGLONG GetRenderedInt32Value(PVOID pRenderedValues, int property); 43 | LONGLONG GetRenderedInt64Value(PVOID pRenderedValues, int property); 44 | ULONGLONG GetRenderedByteValue(PVOID pRenderedValues, int property); 45 | ULONGLONG GetRenderedUInt16Value(PVOID pRenderedValues, int property); 46 | ULONGLONG GetRenderedUInt32Value(PVOID pRenderedValues, int property); 47 | ULONGLONG GetRenderedUInt64Value(PVOID pRenderedValues, int property); 48 | // Returns a pointer to a string that must be freed by the caller 49 | char* GetRenderedStringValue(PVOID pRenderedValues, int property); 50 | // Returns a unix epoch timestamp in milliseconds, not a FileTime 51 | ULONGLONG GetRenderedFileTimeValue(PVOID pRenderedValues, int property); 52 | 53 | // Format the event into a string using details from the event publisher. 54 | // Valid formats are EvtFormatMessage* 55 | char* GetFormattedMessage(ULONGLONG hEventPublisher, ULONGLONG hEvent, int format); 56 | 57 | // Get the handle for the publisher, this must be closed by the caller. 58 | // Needed to format messages since schema is publisher-specific. 59 | ULONGLONG GetEventPublisherHandle(PVOID pRenderedValues); 60 | 61 | // Cast the ULONGLONG back to a pointer and close it 62 | int CloseEvtHandle(ULONGLONG hEvent); 63 | 64 | // Cancel pending operations on a handle 65 | int CancelEvtHandle(ULONGLONG hEvent); 66 | 67 | // Create a context for RenderEventValues that decodes standard system properties. 68 | // Properties in the resulting array can be accessed using the indices from 69 | // EvtSystem* 70 | ULONGLONG CreateSystemRenderContext(); 71 | 72 | // For testing, get a handle on the first event in the log 73 | ULONGLONG GetTestEventHandle(); -------------------------------------------------------------------------------- /event_test.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package winlog 4 | 5 | import ( 6 | "encoding/xml" 7 | . "testing" 8 | "unsafe" 9 | ) 10 | 11 | const ( 12 | SUBSCRIBED_CHANNEL = "test-channel" 13 | ) 14 | 15 | type ProviderXml struct { 16 | ProviderName string `xml:"Name,attr"` 17 | EventSourceName string `xml:"EventSourceName,attr"` 18 | } 19 | 20 | type EventIdXml struct { 21 | EventID uint64 `xml:",chardata"` 22 | Qualifiers uint64 `xml:"Qualifiers,attr"` 23 | } 24 | 25 | type TimeCreatedXml struct { 26 | SystemTime string `xml:"SystemTime,attr"` 27 | } 28 | 29 | type ExecutionXml struct { 30 | ProcessId uint64 `xml:"ProcessID,attr` 31 | } 32 | 33 | type SystemXml struct { 34 | Provider ProviderXml 35 | EventID EventIdXml 36 | 37 | Level uint64 `xml:"Level"` 38 | Task uint64 `xml:"Task"` 39 | Opcode uint64 `xml:"Opcode"` 40 | TimeCreated TimeCreatedXml 41 | RecordId uint64 `xml:"EventRecordID"` 42 | Execution ExecutionXml 43 | Channel string `xml:"Channel"` 44 | ComputerName string `xml:"Computer"` 45 | Version uint64 `xml:"Version"` 46 | } 47 | 48 | type RenderingInfoXml struct { 49 | Msg string `xml:"Message"` 50 | LevelText string `xml:"Level"` 51 | TaskText string `xml:"Task"` 52 | OpcodeText string `xml:"Opcode"` 53 | Keywords []string `xml:"Keywords"` 54 | ChannelText string `xml:"Channel"` 55 | ProviderText string `xml:"Provider"` 56 | } 57 | 58 | type WinLogEventXml struct { 59 | System SystemXml 60 | RenderingInfo RenderingInfoXml 61 | } 62 | 63 | func assertEqual(a, b interface{}, t *T) { 64 | if a != b { 65 | t.Fatalf("%v != %v", a, b) 66 | } 67 | } 68 | 69 | func TestXmlRenderMatchesOurs(t *T) { 70 | testEvent, err := getTestEventHandle() 71 | if err != nil { 72 | t.Fatal(err) 73 | } 74 | defer CloseEventHandle(uint64(testEvent)) 75 | renderContext, err := GetSystemRenderContext() 76 | if err != nil { 77 | t.Fatal(err) 78 | } 79 | defer CloseEventHandle(uint64(renderContext)) 80 | renderedFields, err := RenderEventValues(renderContext, testEvent) 81 | if err != nil { 82 | t.Fatal(err) 83 | } 84 | defer Free(unsafe.Pointer(renderedFields)) 85 | publisherHandle, err := GetEventPublisherHandle(renderedFields) 86 | if err != nil { 87 | t.Fatal(err) 88 | } 89 | defer CloseEventHandle(uint64(publisherHandle)) 90 | xmlString, err := FormatMessage(publisherHandle, testEvent, EvtFormatMessageXml) 91 | if err != nil { 92 | t.Fatal(err) 93 | } 94 | eventXml := WinLogEventXml{} 95 | if err = xml.Unmarshal([]byte(xmlString), &eventXml); err != nil { 96 | t.Fatal(err) 97 | } 98 | 99 | logWatcher, err := NewWinLogWatcher() 100 | defer logWatcher.Shutdown() 101 | event, err := logWatcher.convertEvent(testEvent, SUBSCRIBED_CHANNEL) 102 | if err != nil { 103 | t.Fatal(err) 104 | } 105 | 106 | assertEqual(event.ProviderName, eventXml.System.Provider.ProviderName, t) 107 | assertEqual(event.EventId, eventXml.System.EventID.EventID, t) 108 | assertEqual(event.Qualifiers, eventXml.System.EventID.Qualifiers, t) 109 | assertEqual(event.Level, eventXml.System.Level, t) 110 | assertEqual(event.Task, eventXml.System.Task, t) 111 | assertEqual(event.Opcode, eventXml.System.Opcode, t) 112 | assertEqual(event.RecordId, eventXml.System.RecordId, t) 113 | assertEqual(event.ProcessId, eventXml.System.Execution.ProcessId, t) 114 | assertEqual(event.Channel, eventXml.System.Channel, t) 115 | assertEqual(event.ComputerName, eventXml.System.ComputerName, t) 116 | assertEqual(event.Msg, eventXml.RenderingInfo.Msg, t) 117 | assertEqual(event.LevelText, eventXml.RenderingInfo.LevelText, t) 118 | assertEqual(event.TaskText, eventXml.RenderingInfo.TaskText, t) 119 | assertEqual(event.OpcodeText, eventXml.RenderingInfo.OpcodeText, t) 120 | assertEqual(event.ChannelText, eventXml.RenderingInfo.ChannelText, t) 121 | assertEqual(event.ProviderText, eventXml.RenderingInfo.ProviderText, t) 122 | assertEqual(event.Created.Format("2006-01-02T15:04:05.000000000Z"), eventXml.System.TimeCreated.SystemTime, t) 123 | assertEqual(event.SubscribedChannel, SUBSCRIBED_CHANNEL, t) 124 | } 125 | 126 | func BenchmarkXmlDecode(b *B) { 127 | testEvent, err := getTestEventHandle() 128 | if err != nil { 129 | b.Fatal(err) 130 | } 131 | defer CloseEventHandle(uint64(testEvent)) 132 | renderContext, err := GetSystemRenderContext() 133 | if err != nil { 134 | b.Fatal(err) 135 | } 136 | defer CloseEventHandle(uint64(renderContext)) 137 | for i := 0; i < b.N; i++ { 138 | renderedFields, err := RenderEventValues(renderContext, testEvent) 139 | if err != nil { 140 | b.Fatal(err) 141 | } 142 | publisherHandle, err := GetEventPublisherHandle(renderedFields) 143 | if err != nil { 144 | b.Fatal(err) 145 | } 146 | xmlString, err := FormatMessage(publisherHandle, testEvent, EvtFormatMessageXml) 147 | if err != nil { 148 | b.Fatal(err) 149 | } 150 | eventXml := WinLogEventXml{} 151 | if err = xml.Unmarshal([]byte(xmlString), &eventXml); err != nil { 152 | b.Fatal(err) 153 | } 154 | Free(unsafe.Pointer(renderedFields)) 155 | CloseEventHandle(uint64(publisherHandle)) 156 | } 157 | } 158 | 159 | func BenchmarkAPIDecode(b *B) { 160 | testEvent, err := getTestEventHandle() 161 | if err != nil { 162 | b.Fatal(err) 163 | } 164 | defer CloseEventHandle(uint64(testEvent)) 165 | renderContext, err := GetSystemRenderContext() 166 | if err != nil { 167 | b.Fatal(err) 168 | } 169 | defer CloseEventHandle(uint64(renderContext)) 170 | logWatcher, err := NewWinLogWatcher() 171 | for i := 0; i < b.N; i++ { 172 | _, err := logWatcher.convertEvent(testEvent, SUBSCRIBED_CHANNEL) 173 | if err != nil { 174 | b.Fatal(err) 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/scalingdata/gowinlog" 6 | ) 7 | 8 | func main() { 9 | watcher, err := winlog.NewWinLogWatcher() 10 | if err != nil { 11 | fmt.Printf("Couldn't create watcher: %v\n", err) 12 | return 13 | } 14 | err = watcher.SubscribeFromBeginning("Application") 15 | if err != nil { 16 | fmt.Printf("Couldn't subscribe to Application: %v", err) 17 | } 18 | for { 19 | select { 20 | case evt := <-watcher.Event(): 21 | fmt.Printf("Event: %v\n", evt) 22 | bookmark := evt.GetBookmark() 23 | fmt.Printf("Bookmark: %v\n", bookmark) 24 | case err := <-watcher.Error(): 25 | fmt.Printf("Error: %v\n\n", err) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /structs.go: -------------------------------------------------------------------------------- 1 | package winlog 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | "unsafe" 7 | ) 8 | 9 | // Stores the common fields from a log event 10 | type WinLogEvent struct { 11 | // From EvtRender 12 | ProviderName string 13 | EventId uint64 14 | Qualifiers uint64 15 | Level uint64 16 | Task uint64 17 | Opcode uint64 18 | Created time.Time 19 | RecordId uint64 20 | ProcessId uint64 21 | ThreadId uint64 22 | Channel string 23 | ComputerName string 24 | Version uint64 25 | RenderedFieldsErr error 26 | 27 | // From EvtFormatMessage 28 | Msg string 29 | LevelText string 30 | TaskText string 31 | OpcodeText string 32 | Keywords []string 33 | ChannelText string 34 | ProviderText string 35 | IdText string 36 | PublisherHandleErr error 37 | 38 | // XML body 39 | Xml string 40 | XmlErr error 41 | 42 | // Serialied XML bookmark to 43 | // restart at this event 44 | Bookmark string 45 | 46 | // Subscribed channel from which the event was retrieved, 47 | // which may be different than the event's channel 48 | SubscribedChannel string 49 | } 50 | 51 | type channelWatcher struct { 52 | subscription ListenerHandle 53 | callback *LogEventCallbackWrapper 54 | bookmark BookmarkHandle 55 | } 56 | 57 | // Watches one or more event log channels 58 | // and publishes events and errors to Go 59 | // channels 60 | type WinLogWatcher struct { 61 | errChan chan error 62 | eventChan chan *WinLogEvent 63 | 64 | renderContext SysRenderContext 65 | watches map[string]*channelWatcher 66 | watchMutex sync.Mutex 67 | shutdown chan interface{} 68 | 69 | // Optionally render localized fields. EvtFormatMessage() is slow, so 70 | // skipping these fields provides a big speedup. 71 | renderMessage bool 72 | renderLevel bool 73 | renderTask bool 74 | renderProvider bool 75 | renderOpcode bool 76 | renderChannel bool 77 | renderId bool 78 | } 79 | 80 | type SysRenderContext uint64 81 | type ListenerHandle uint64 82 | type PublisherHandle uint64 83 | type EventHandle uint64 84 | type RenderedFields unsafe.Pointer 85 | type BookmarkHandle uint64 86 | 87 | type LogEventCallback interface { 88 | PublishError(error) 89 | PublishEvent(EventHandle, string) 90 | } 91 | 92 | type LogEventCallbackWrapper struct { 93 | callback LogEventCallback 94 | subscribedChannel string 95 | } 96 | -------------------------------------------------------------------------------- /winlog.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package winlog 4 | 5 | import ( 6 | "fmt" 7 | "time" 8 | "unsafe" 9 | ) 10 | 11 | func (self *WinLogWatcher) Event() <-chan *WinLogEvent { 12 | return self.eventChan 13 | } 14 | 15 | func (self *WinLogWatcher) Error() <-chan error { 16 | return self.errChan 17 | } 18 | 19 | // Create a new watcher 20 | func NewWinLogWatcher() (*WinLogWatcher, error) { 21 | cHandle, err := GetSystemRenderContext() 22 | if err != nil { 23 | return nil, err 24 | } 25 | return &WinLogWatcher{ 26 | shutdown: make(chan interface{}), 27 | errChan: make(chan error), 28 | eventChan: make(chan *WinLogEvent), 29 | renderContext: cHandle, 30 | watches: make(map[string]*channelWatcher), 31 | renderMessage: true, 32 | renderLevel: true, 33 | renderTask: true, 34 | renderProvider: true, 35 | renderOpcode: true, 36 | renderChannel: true, 37 | renderId: true, 38 | }, nil 39 | } 40 | 41 | // Whether to use EvtFormatMessage to render the event message 42 | func (self *WinLogWatcher) SetRenderMessage(render bool) { 43 | self.renderMessage = render 44 | } 45 | 46 | // Whether to use EvtFormatMessage to render the event level 47 | func (self *WinLogWatcher) SetRenderLevel(render bool) { 48 | self.renderLevel = render 49 | } 50 | 51 | // Whether to use EvtFormatMessage to render the event task 52 | func (self *WinLogWatcher) SetRenderTask(render bool) { 53 | self.renderTask = render 54 | } 55 | 56 | // Whether to use EvtFormatMessage to render the event provider 57 | func (self *WinLogWatcher) SetRenderProvider(render bool) { 58 | self.renderProvider = render 59 | } 60 | 61 | // Whether to use EvtFormatMessage to render the event opcode 62 | func (self *WinLogWatcher) SetRenderOpcode(render bool) { 63 | self.renderOpcode = render 64 | } 65 | 66 | // Whether to use EvtFormatMessage to render the event channel 67 | func (self *WinLogWatcher) SetRenderChannel(render bool) { 68 | self.renderChannel = render 69 | } 70 | 71 | // Whether to use EvtFormatMessage to render the event ID 72 | func (self *WinLogWatcher) SetRenderId(render bool) { 73 | self.renderId = render 74 | } 75 | 76 | // Subscribe to a Windows Event Log channel, starting with the first event 77 | // in the log. `query` is an XPath expression for filtering events: to recieve 78 | // all events on the channel, use "*" as the query. 79 | func (self *WinLogWatcher) SubscribeFromBeginning(channel, query string) error { 80 | return self.subscribeWithoutBookmark(channel, query, EvtSubscribeStartAtOldestRecord) 81 | } 82 | 83 | // Subscribe to a Windows Event Log channel, starting with the next event 84 | // that arrives. `query` is an XPath expression for filtering events: to recieve 85 | // all events on the channel, use "*" as the query. 86 | func (self *WinLogWatcher) SubscribeFromNow(channel, query string) error { 87 | return self.subscribeWithoutBookmark(channel, query, EvtSubscribeToFutureEvents) 88 | } 89 | 90 | func (self *WinLogWatcher) subscribeWithoutBookmark(channel, query string, flags EVT_SUBSCRIBE_FLAGS) error { 91 | self.watchMutex.Lock() 92 | defer self.watchMutex.Unlock() 93 | if _, ok := self.watches[channel]; ok { 94 | return fmt.Errorf("A watcher for channel %q already exists", channel) 95 | } 96 | newBookmark, err := CreateBookmark() 97 | if err != nil { 98 | return fmt.Errorf("Failed to create new bookmark handle: %v", err) 99 | } 100 | callback := &LogEventCallbackWrapper{callback: self, subscribedChannel: channel} 101 | subscription, err := CreateListener(channel, query, flags, callback) 102 | if err != nil { 103 | CloseEventHandle(uint64(newBookmark)) 104 | return err 105 | } 106 | self.watches[channel] = &channelWatcher{ 107 | bookmark: newBookmark, 108 | subscription: subscription, 109 | callback: callback, 110 | } 111 | return nil 112 | } 113 | 114 | // Subscribe to a Windows Event Log channel, starting with the first event in the log 115 | // after the bookmarked event. There may be a gap if events have been purged. `query` 116 | // is an XPath expression for filtering events: to recieve all events on the channel, 117 | // use "*" as the query 118 | func (self *WinLogWatcher) SubscribeFromBookmark(channel, query string, xmlString string) error { 119 | self.watchMutex.Lock() 120 | defer self.watchMutex.Unlock() 121 | if _, ok := self.watches[channel]; ok { 122 | return fmt.Errorf("A watcher for channel %q already exists", channel) 123 | } 124 | callback := &LogEventCallbackWrapper{callback: self, subscribedChannel: channel} 125 | bookmark, err := CreateBookmarkFromXml(xmlString) 126 | if err != nil { 127 | return fmt.Errorf("Failed to create new bookmark handle: %v", err) 128 | } 129 | subscription, err := CreateListenerFromBookmark(channel, query, callback, bookmark) 130 | if err != nil { 131 | CloseEventHandle(uint64(bookmark)) 132 | return fmt.Errorf("Failed to add listener: %v", err) 133 | } 134 | self.watches[channel] = &channelWatcher{ 135 | bookmark: bookmark, 136 | subscription: subscription, 137 | callback: callback, 138 | } 139 | return nil 140 | } 141 | 142 | func (self *WinLogWatcher) removeSubscription(channel string, watch *channelWatcher) error { 143 | cancelErr := CancelEventHandle(uint64(watch.subscription)) 144 | closeErr := CloseEventHandle(uint64(watch.subscription)) 145 | CloseEventHandle(uint64(watch.bookmark)) 146 | self.watchMutex.Lock() 147 | delete(self.watches, channel) 148 | self.watchMutex.Unlock() 149 | if cancelErr != nil { 150 | return cancelErr 151 | } 152 | return closeErr 153 | } 154 | 155 | // Remove all subscriptions from this watcher and shut down. 156 | func (self *WinLogWatcher) Shutdown() { 157 | self.watchMutex.Lock() 158 | watches := self.watches 159 | self.watchMutex.Unlock() 160 | close(self.shutdown) 161 | for channel, watch := range watches { 162 | self.removeSubscription(channel, watch) 163 | } 164 | CloseEventHandle(uint64(self.renderContext)) 165 | close(self.errChan) 166 | close(self.eventChan) 167 | } 168 | 169 | func (self *WinLogWatcher) PublishError(err error) { 170 | // Publish the received error to the errChan, but 171 | // discard if shutdown is in progress 172 | select { 173 | case self.errChan <- err: 174 | case <-self.shutdown: 175 | } 176 | } 177 | 178 | func (self *WinLogWatcher) convertEvent(handle EventHandle, subscribedChannel string) (*WinLogEvent, error) { 179 | // Rendered values 180 | var computerName, providerName, channel string 181 | var level, task, opcode, recordId, qualifiers, eventId, processId, threadId, version uint64 182 | var created time.Time 183 | 184 | // Localized fields 185 | var msgText, lvlText, taskText, providerText, opcodeText, channelText, idText string 186 | 187 | // Publisher fields 188 | var publisherHandle PublisherHandle 189 | var publisherHandleErr error 190 | 191 | // Render XML, any error is stored in the returned WinLogEvent 192 | xml, xmlErr := RenderEventXML(handle) 193 | 194 | // Render the values 195 | renderedFields, renderedFieldsErr := RenderEventValues(self.renderContext, handle) 196 | if renderedFieldsErr == nil { 197 | // If fields don't exist we include the nil value 198 | computerName, _ = RenderStringField(renderedFields, EvtSystemComputer) 199 | providerName, _ = RenderStringField(renderedFields, EvtSystemProviderName) 200 | channel, _ = RenderStringField(renderedFields, EvtSystemChannel) 201 | level, _ = RenderUIntField(renderedFields, EvtSystemLevel) 202 | task, _ = RenderUIntField(renderedFields, EvtSystemTask) 203 | opcode, _ = RenderUIntField(renderedFields, EvtSystemOpcode) 204 | recordId, _ = RenderUIntField(renderedFields, EvtSystemEventRecordId) 205 | qualifiers, _ = RenderUIntField(renderedFields, EvtSystemQualifiers) 206 | eventId, _ = RenderUIntField(renderedFields, EvtSystemEventID) 207 | processId, _ = RenderUIntField(renderedFields, EvtSystemProcessID) 208 | threadId, _ = RenderUIntField(renderedFields, EvtSystemThreadID) 209 | version, _ = RenderUIntField(renderedFields, EvtSystemVersion) 210 | created, _ = RenderFileTimeField(renderedFields, EvtSystemTimeCreated) 211 | 212 | // Render localized fields 213 | publisherHandle, publisherHandleErr = GetEventPublisherHandle(renderedFields) 214 | if publisherHandleErr == nil { 215 | if self.renderMessage { 216 | msgText, _ = FormatMessage(publisherHandle, handle, EvtFormatMessageEvent) 217 | } 218 | 219 | if self.renderLevel { 220 | lvlText, _ = FormatMessage(publisherHandle, handle, EvtFormatMessageLevel) 221 | } 222 | 223 | if self.renderTask { 224 | taskText, _ = FormatMessage(publisherHandle, handle, EvtFormatMessageTask) 225 | } 226 | 227 | if self.renderProvider { 228 | providerText, _ = FormatMessage(publisherHandle, handle, EvtFormatMessageProvider) 229 | } 230 | 231 | if self.renderOpcode { 232 | opcodeText, _ = FormatMessage(publisherHandle, handle, EvtFormatMessageOpcode) 233 | } 234 | 235 | if self.renderChannel { 236 | channelText, _ = FormatMessage(publisherHandle, handle, EvtFormatMessageChannel) 237 | } 238 | 239 | if self.renderId { 240 | idText, _ = FormatMessage(publisherHandle, handle, EvtFormatMessageId) 241 | } 242 | } 243 | 244 | CloseEventHandle(uint64(publisherHandle)) 245 | Free(unsafe.Pointer(renderedFields)) 246 | } 247 | 248 | // Return an error if we couldn't render anything useful 249 | if xmlErr != nil && renderedFieldsErr != nil { 250 | return nil, fmt.Errorf("Failed to render event values and XML: %v", []error{renderedFieldsErr, xmlErr}) 251 | } 252 | 253 | event := WinLogEvent{ 254 | Xml: xml, 255 | XmlErr: xmlErr, 256 | 257 | ProviderName: providerName, 258 | EventId: eventId, 259 | Qualifiers: qualifiers, 260 | Level: level, 261 | Task: task, 262 | Opcode: opcode, 263 | Created: created, 264 | RecordId: recordId, 265 | ProcessId: processId, 266 | ThreadId: threadId, 267 | Channel: channel, 268 | ComputerName: computerName, 269 | Version: version, 270 | RenderedFieldsErr: renderedFieldsErr, 271 | 272 | Msg: msgText, 273 | LevelText: lvlText, 274 | TaskText: taskText, 275 | OpcodeText: opcodeText, 276 | ChannelText: channelText, 277 | ProviderText: providerText, 278 | IdText: idText, 279 | PublisherHandleErr: publisherHandleErr, 280 | 281 | SubscribedChannel: subscribedChannel, 282 | } 283 | return &event, nil 284 | } 285 | 286 | func (self *WinLogWatcher) PublishEvent(handle EventHandle, subscribedChannel string) { 287 | 288 | // Convert the event from the event log schema 289 | event, err := self.convertEvent(handle, subscribedChannel) 290 | if err != nil { 291 | self.PublishError(err) 292 | return 293 | } 294 | 295 | // Get the bookmark for the channel 296 | self.watchMutex.Lock() 297 | watch, ok := self.watches[subscribedChannel] 298 | self.watchMutex.Unlock() 299 | if !ok { 300 | self.errChan <- fmt.Errorf("No handle for channel bookmark %q", subscribedChannel) 301 | return 302 | } 303 | 304 | // Update the bookmark with the current event 305 | UpdateBookmark(watch.bookmark, handle) 306 | 307 | // Serialize the boomark as XML and include it in the event 308 | bookmarkXml, err := RenderBookmark(watch.bookmark) 309 | if err != nil { 310 | self.PublishError(fmt.Errorf("Error rendering bookmark for event - %v", err)) 311 | return 312 | } 313 | event.Bookmark = bookmarkXml 314 | 315 | // Don't block when shutting down if the consumer has gone away 316 | select { 317 | case self.eventChan <- event: 318 | case <-self.shutdown: 319 | return 320 | } 321 | 322 | } 323 | -------------------------------------------------------------------------------- /winlog_test.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package winlog 4 | 5 | import ( 6 | . "testing" 7 | ) 8 | 9 | func TestWinlogWatcherConfiguresRendering(t *T) { 10 | watcher, err := NewWinLogWatcher() 11 | assertEqual(err, nil, t) 12 | 13 | watcher.SetRenderMessage(false) 14 | watcher.SetRenderLevel(false) 15 | watcher.SetRenderTask(false) 16 | watcher.SetRenderProvider(false) 17 | watcher.SetRenderOpcode(false) 18 | watcher.SetRenderChannel(false) 19 | watcher.SetRenderId(false) 20 | 21 | assertEqual(watcher.renderMessage, false, t) 22 | assertEqual(watcher.renderLevel, false, t) 23 | assertEqual(watcher.renderTask, false, t) 24 | assertEqual(watcher.renderProvider, false, t) 25 | assertEqual(watcher.renderOpcode, false, t) 26 | assertEqual(watcher.renderChannel, false, t) 27 | assertEqual(watcher.renderId, false, t) 28 | 29 | watcher.SetRenderMessage(true) 30 | watcher.SetRenderLevel(true) 31 | watcher.SetRenderTask(true) 32 | watcher.SetRenderProvider(true) 33 | watcher.SetRenderOpcode(true) 34 | watcher.SetRenderChannel(true) 35 | watcher.SetRenderId(true) 36 | 37 | assertEqual(watcher.renderMessage, true, t) 38 | assertEqual(watcher.renderLevel, true, t) 39 | assertEqual(watcher.renderTask, true, t) 40 | assertEqual(watcher.renderProvider, true, t) 41 | assertEqual(watcher.renderOpcode, true, t) 42 | assertEqual(watcher.renderChannel, true, t) 43 | assertEqual(watcher.renderId, true, t) 44 | 45 | watcher.Shutdown() 46 | } 47 | --------------------------------------------------------------------------------