forked from moby/moby
-
Notifications
You must be signed in to change notification settings - Fork 0
/
etwlogs_windows.go
170 lines (150 loc) · 4.74 KB
/
etwlogs_windows.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// Package etwlogs provides a log driver for forwarding container logs
// as ETW events.(ETW stands for Event Tracing for Windows)
// A client can then create an ETW listener to listen for events that are sent
// by the ETW provider that we register, using the provider's GUID "a3693192-9ed6-46d2-a981-f8226c8363bd".
// Here is an example of how to do this using the logman utility:
// 1. logman start -ets DockerContainerLogs -p {a3693192-9ed6-46d2-a981-f8226c8363bd} 0 0 -o trace.etl
// 2. Run container(s) and generate log messages
// 3. logman stop -ets DockerContainerLogs
// 4. You can then convert the etl log file to XML using: tracerpt -y trace.etl
//
// Each container log message generates an ETW event that also contains:
// the container name and ID, the timestamp, and the stream type.
package etwlogs
import (
"errors"
"fmt"
"sync"
"syscall"
"unsafe"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/logger"
"golang.org/x/sys/windows"
)
type etwLogs struct {
containerName string
imageName string
containerID string
imageID string
}
const (
name = "etwlogs"
win32CallSuccess = 0
)
var (
modAdvapi32 = windows.NewLazySystemDLL("Advapi32.dll")
procEventRegister = modAdvapi32.NewProc("EventRegister")
procEventWriteString = modAdvapi32.NewProc("EventWriteString")
procEventUnregister = modAdvapi32.NewProc("EventUnregister")
)
var providerHandle syscall.Handle
var refCount int
var mu sync.Mutex
func init() {
providerHandle = syscall.InvalidHandle
if err := logger.RegisterLogDriver(name, New); err != nil {
logrus.Fatal(err)
}
}
// New creates a new etwLogs logger for the given container and registers the EWT provider.
func New(ctx logger.Context) (logger.Logger, error) {
if err := registerETWProvider(); err != nil {
return nil, err
}
logrus.Debugf("logging driver etwLogs configured for container: %s.", ctx.ContainerID)
return &etwLogs{
containerName: fixContainerName(ctx.ContainerName),
imageName: ctx.ContainerImageName,
containerID: ctx.ContainerID,
imageID: ctx.ContainerImageID,
}, nil
}
// Log logs the message to the ETW stream.
func (etwLogger *etwLogs) Log(msg *logger.Message) error {
if providerHandle == syscall.InvalidHandle {
// This should never be hit, if it is, it indicates a programming error.
errorMessage := "ETWLogs cannot log the message, because the event provider has not been registered."
logrus.Error(errorMessage)
return errors.New(errorMessage)
}
return callEventWriteString(createLogMessage(etwLogger, msg))
}
// Close closes the logger by unregistering the ETW provider.
func (etwLogger *etwLogs) Close() error {
unregisterETWProvider()
return nil
}
func (etwLogger *etwLogs) Name() string {
return name
}
func createLogMessage(etwLogger *etwLogs, msg *logger.Message) string {
return fmt.Sprintf("container_name: %s, image_name: %s, container_id: %s, image_id: %s, source: %s, log: %s",
etwLogger.containerName,
etwLogger.imageName,
etwLogger.containerID,
etwLogger.imageID,
msg.Source,
msg.Line)
}
// fixContainerName removes the initial '/' from the container name.
func fixContainerName(cntName string) string {
if len(cntName) > 0 && cntName[0] == '/' {
cntName = cntName[1:]
}
return cntName
}
func registerETWProvider() error {
mu.Lock()
defer mu.Unlock()
if refCount == 0 {
var err error
if err = callEventRegister(); err != nil {
return err
}
}
refCount++
return nil
}
func unregisterETWProvider() {
mu.Lock()
defer mu.Unlock()
if refCount == 1 {
if callEventUnregister() {
refCount--
providerHandle = syscall.InvalidHandle
}
// Not returning an error if EventUnregister fails, because etwLogs will continue to work
} else {
refCount--
}
}
func callEventRegister() error {
// The provider's GUID is {a3693192-9ed6-46d2-a981-f8226c8363bd}
guid := syscall.GUID{
0xa3693192, 0x9ed6, 0x46d2,
[8]byte{0xa9, 0x81, 0xf8, 0x22, 0x6c, 0x83, 0x63, 0xbd},
}
ret, _, _ := procEventRegister.Call(uintptr(unsafe.Pointer(&guid)), 0, 0, uintptr(unsafe.Pointer(&providerHandle)))
if ret != win32CallSuccess {
errorMessage := fmt.Sprintf("Failed to register ETW provider. Error: %d", ret)
logrus.Error(errorMessage)
return errors.New(errorMessage)
}
return nil
}
func callEventWriteString(message string) error {
ret, _, _ := procEventWriteString.Call(uintptr(providerHandle), 0, 0, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(message))))
if ret != win32CallSuccess {
errorMessage := fmt.Sprintf("ETWLogs provider failed to log message. Error: %d", ret)
logrus.Error(errorMessage)
return errors.New(errorMessage)
}
return nil
}
func callEventUnregister() bool {
ret, _, _ := procEventUnregister.Call(uintptr(providerHandle))
if ret != win32CallSuccess {
return false
}
return true
}