Browse code

Windows: Add ETW logging driver plug-in

Signed-off-by: Cedric Davies <cedricda@microsoft.com>

Cedric Davies authored on 2016/01/26 07:49:52
Showing 9 changed files
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	// Importing packages here only to make sure their init gets called and
5 5
 	// therefore they register themselves to the logdriver factory.
6 6
 	_ "github.com/docker/docker/daemon/logger/awslogs"
7
+	_ "github.com/docker/docker/daemon/logger/etwlogs"
7 8
 	_ "github.com/docker/docker/daemon/logger/jsonfilelog"
8 9
 	_ "github.com/docker/docker/daemon/logger/splunk"
9 10
 )
10 11
new file mode 100644
... ...
@@ -0,0 +1,183 @@
0
+// Package etwlogs provides a log driver for forwarding container logs
1
+// as ETW events.(ETW stands for Event Tracing for Windows)
2
+// A client can then create an ETW listener to listen for events that are sent
3
+// by the ETW provider that we register, using the provider's GUID "a3693192-9ed6-46d2-a981-f8226c8363bd".
4
+// Here is an example of how to do this using the logman utility:
5
+// 1. logman start -ets DockerContainerLogs -p {a3693192-9ed6-46d2-a981-f8226c8363bd} 0 0 -o trace.etl
6
+// 2. Run container(s) and generate log messages
7
+// 3. logman stop -ets DockerContainerLogs
8
+// 4. You can then convert the etl log file to XML using: tracerpt -y trace.etl
9
+//
10
+// Each container log message generates a ETW event that also contains:
11
+// the container name and ID, the timestamp, and the stream type.
12
+package etwlogs
13
+
14
+import (
15
+	"errors"
16
+	"fmt"
17
+	"sync"
18
+	"syscall"
19
+	"unsafe"
20
+
21
+	"github.com/Sirupsen/logrus"
22
+	"github.com/docker/docker/daemon/logger"
23
+)
24
+
25
+type etwLogs struct {
26
+	containerName string
27
+	imageName     string
28
+	containerID   string
29
+	imageID       string
30
+}
31
+
32
+const (
33
+	name             = "etwlogs"
34
+	win32CallSuccess = 0
35
+)
36
+
37
+var win32Lib *syscall.DLL
38
+var providerHandle syscall.Handle
39
+var refCount int
40
+var mu sync.Mutex
41
+
42
+func init() {
43
+	providerHandle = syscall.InvalidHandle
44
+	if err := logger.RegisterLogDriver(name, New); err != nil {
45
+		logrus.Fatal(err)
46
+	}
47
+}
48
+
49
+// New creates a new etwLogs logger for the given container and registers the EWT provider.
50
+func New(ctx logger.Context) (logger.Logger, error) {
51
+	if err := registerETWProvider(); err != nil {
52
+		return nil, err
53
+	}
54
+	logrus.Debugf("logging driver etwLogs configured for container: %s.", ctx.ContainerID)
55
+
56
+	return &etwLogs{
57
+		containerName: fixContainerName(ctx.ContainerName),
58
+		imageName:     ctx.ContainerImageName,
59
+		containerID:   ctx.ContainerID,
60
+		imageID:       ctx.ContainerImageID,
61
+	}, nil
62
+}
63
+
64
+// Log logs the message to the ETW stream.
65
+func (etwLogger *etwLogs) Log(msg *logger.Message) error {
66
+	if providerHandle == syscall.InvalidHandle {
67
+		// This should never be hit, if it is, it indicates a programming error.
68
+		errorMessage := "ETWLogs cannot log the message, because the event provider has not been registered."
69
+		logrus.Error(errorMessage)
70
+		return errors.New(errorMessage)
71
+	}
72
+	return callEventWriteString(createLogMessage(etwLogger, msg))
73
+}
74
+
75
+// Close closes the logger by unregistering the ETW provider.
76
+func (etwLogger *etwLogs) Close() error {
77
+	unregisterETWProvider()
78
+	return nil
79
+}
80
+
81
+func (etwLogger *etwLogs) Name() string {
82
+	return name
83
+}
84
+
85
+func createLogMessage(etwLogger *etwLogs, msg *logger.Message) string {
86
+	return fmt.Sprintf("container_name: %s, image_name: %s, container_id: %s, image_id: %s, source: %s, log: %s",
87
+		etwLogger.containerName,
88
+		etwLogger.imageName,
89
+		etwLogger.containerID,
90
+		etwLogger.imageID,
91
+		msg.Source,
92
+		msg.Line)
93
+}
94
+
95
+// fixContainerName removes the initial '/' from the container name.
96
+func fixContainerName(cntName string) string {
97
+	if len(cntName) > 0 && cntName[0] == '/' {
98
+		cntName = cntName[1:]
99
+	}
100
+	return cntName
101
+}
102
+
103
+func registerETWProvider() error {
104
+	mu.Lock()
105
+	defer mu.Unlock()
106
+	if refCount == 0 {
107
+		var err error
108
+		if win32Lib, err = syscall.LoadDLL("Advapi32.dll"); err != nil {
109
+			return err
110
+		}
111
+		if err = callEventRegister(); err != nil {
112
+			win32Lib.Release()
113
+			win32Lib = nil
114
+			return err
115
+		}
116
+	}
117
+
118
+	refCount++
119
+	return nil
120
+}
121
+
122
+func unregisterETWProvider() {
123
+	mu.Lock()
124
+	defer mu.Unlock()
125
+	if refCount == 1 {
126
+		if callEventUnregister() {
127
+			refCount--
128
+			providerHandle = syscall.InvalidHandle
129
+			win32Lib.Release()
130
+			win32Lib = nil
131
+		}
132
+		// Not returning an error if EventUnregister fails, because etwLogs will continue to work
133
+	} else {
134
+		refCount--
135
+	}
136
+}
137
+
138
+func callEventRegister() error {
139
+	proc, err := win32Lib.FindProc("EventRegister")
140
+	if err != nil {
141
+		return err
142
+	}
143
+	// The provider's GUID is {a3693192-9ed6-46d2-a981-f8226c8363bd}
144
+	guid := syscall.GUID{
145
+		0xa3693192, 0x9ed6, 0x46d2,
146
+		[8]byte{0xa9, 0x81, 0xf8, 0x22, 0x6c, 0x83, 0x63, 0xbd},
147
+	}
148
+
149
+	ret, _, _ := proc.Call(uintptr(unsafe.Pointer(&guid)), 0, 0, uintptr(unsafe.Pointer(&providerHandle)))
150
+	if ret != win32CallSuccess {
151
+		errorMessage := fmt.Sprintf("Failed to register ETW provider. Error: %d", ret)
152
+		logrus.Error(errorMessage)
153
+		return errors.New(errorMessage)
154
+	}
155
+	return nil
156
+}
157
+
158
+func callEventWriteString(message string) error {
159
+	proc, err := win32Lib.FindProc("EventWriteString")
160
+	if err != nil {
161
+		return err
162
+	}
163
+	ret, _, _ := proc.Call(uintptr(providerHandle), 0, 0, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(message))))
164
+	if ret != win32CallSuccess {
165
+		errorMessage := fmt.Sprintf("ETWLogs provider failed to log message. Error: %d", ret)
166
+		logrus.Error(errorMessage)
167
+		return errors.New(errorMessage)
168
+	}
169
+	return nil
170
+}
171
+
172
+func callEventUnregister() bool {
173
+	proc, err := win32Lib.FindProc("EventUnregister")
174
+	if err != nil {
175
+		return false
176
+	}
177
+	ret, _, _ := proc.Call(uintptr(providerHandle))
178
+	if ret != win32CallSuccess {
179
+		return false
180
+	}
181
+	return true
182
+}
0 183
new file mode 100644
... ...
@@ -0,0 +1,69 @@
0
+<!--[metadata]>
1
+title = "ETW logging driver"
2
+description = "Describes how to use the etwlogs logging driver."
3
+keywords = ["ETW, docker, logging, driver"]
4
+[menu.main]
5
+parent = "smn_logging" 
6
+weight=2
7
+<![end-metadata]-->
8
+
9
+
10
+# ETW logging driver
11
+
12
+The ETW logging driver forwards container logs as ETW events. 
13
+ETW stands for Event Tracing in Windows, and is the common framework
14
+for tracing applications in Windows. Each ETW event contains a message
15
+with both the log and its context information. A client can then create
16
+an ETW listener to listen to these events. 
17
+
18
+The ETW provider that this logging driver registers with Windows, has the 
19
+GUID identifier of: `{a3693192-9ed6-46d2-a981-f8226c8363bd}`. A client creates an 
20
+ETW listener and registers to listen to events from the logging driver's provider. 
21
+It does not matter the order in which the provider and listener are created. 
22
+A client can create their ETW listener and start listening for events from the provider, 
23
+before the provider has been registered with the system. 
24
+
25
+## Usage
26
+
27
+Here is an example of how to listen to these events using the logman utility program 
28
+included in most installations of Windows:
29
+
30
+   1. `logman start -ets DockerContainerLogs -p {a3693192-9ed6-46d2-a981-f8226c8363bd} 0 0 -o trace.etl`
31
+   2. Run your container(s) with the etwlogs driver, by adding `--log-driver=etwlogs` 
32
+   to the Docker run command, and generate log messages.
33
+   3. `logman stop -ets DockerContainerLogs`
34
+   4. This will generate an etl file that contains the events. One way to convert this file into 
35
+   human-readable form is to run: `tracerpt -y trace.etl`. 
36
+   
37
+Each ETW event will contain a structured message string in this format:
38
+
39
+    container_name: %s, image_name: %s, container_id: %s, image_id: %s, source: [stdout | stderr], log: %s
40
+
41
+Details on each item in the message can be found below:
42
+
43
+| Field                | Description                                     |
44
+-----------------------|-------------------------------------------------|
45
+| `container_name`     | The container name at the time it was started.  |
46
+| `image_name`         | The name of the container's image.              |
47
+| `container_id`       | The full 64-character container ID.             |
48
+| `image_id`           | The full ID of the container's image.           |
49
+| `source`             | `stdout` or `stderr`.                           |
50
+| `log`                | The container log message.                      |
51
+
52
+Here is an example event message:
53
+
54
+    container_name: backstabbing_spence, 
55
+    image_name: windowsservercore, 
56
+    container_id: f14bb55aa862d7596b03a33251c1be7dbbec8056bbdead1da8ec5ecebbe29731, 
57
+    image_id: sha256:2f9e19bd998d3565b4f345ac9aaf6e3fc555406239a4fb1b1ba879673713824b, 
58
+    source: stdout, 
59
+    log: Hello world!
60
+
61
+A client can parse this message string to get both the log message, as well as its 
62
+context information. Note that the time stamp is also available within the ETW event. 
63
+
64
+**Note**  This ETW provider emits only a message string, and not a specially 
65
+structured ETW event. Therefore, it is not required to register a manifest file 
66
+with the system to read and interpret its ETW events.
... ...
@@ -20,3 +20,4 @@ weight=8
20 20
 * [Journald logging driver](journald.md)
21 21
 * [Amazon CloudWatch Logs logging driver](awslogs.md)
22 22
 * [Splunk logging driver](splunk.md)
23
+* [ETW logging driver](etwlogs.md)
... ...
@@ -26,6 +26,7 @@ container's logging driver. The following options are supported:
26 26
 | `fluentd`   | Fluentd logging driver for Docker. Writes log messages to `fluentd` (forward input).                                          |
27 27
 | `awslogs`   | Amazon CloudWatch Logs logging driver for Docker. Writes log messages to Amazon CloudWatch Logs.                              |
28 28
 | `splunk`    | Splunk logging driver for Docker. Writes log messages to `splunk` using HTTP Event Collector.                                 |
29
+| `etwlogs`   | ETW logging driver for Docker on Windows. Writes log messages as ETW events.                                                  |
29 30
 
30 31
 The `docker logs`command is available only for the `json-file` and `journald`
31 32
 logging drivers.
... ...
@@ -204,3 +205,12 @@ The Splunk logging driver requires the following options:
204 204
 
205 205
 For detailed information about working with this logging driver, see the [Splunk logging driver](splunk.md)
206 206
 reference documentation.
207
+
208
+## ETW logging driver options
209
+
210
+The etwlogs logging driver does not require any options to be specified. This logging driver will forward each log message
211
+as an ETW event. An ETW listener can then be created to listen for these events. 
212
+
213
+For detailed information on working with this logging driver, see [the ETW logging driver](etwlogs.md) reference documentation.
214
+
215
+
... ...
@@ -402,7 +402,7 @@ Json Parameters:
402 402
         systems, such as SELinux.
403 403
     -   **LogConfig** - Log configuration for the container, specified as a JSON object in the form
404 404
           `{ "Type": "<driver_name>", "Config": {"key1": "val1"}}`.
405
-          Available types: `json-file`, `syslog`, `journald`, `gelf`, `awslogs`, `splunk`, `none`.
405
+          Available types: `json-file`, `syslog`, `journald`, `gelf`, `fluentd`, `awslogs`, `splunk`, `etwlogs`, `none`.
406 406
           `json-file` logging driver.
407 407
     -   **CgroupParent** - Path to `cgroups` under which the container's `cgroup` is created. If the path is not absolute, the path is considered to be relative to the `cgroups` path of the init process. Cgroups are created if they do not already exist.
408 408
     -   **VolumeDriver** - Driver that this container users to mount volumes.
... ...
@@ -214,7 +214,7 @@ millions of trillions.
214 214
    Add link to another container in the form of <name or id>:alias or just
215 215
    <name or id> in which case the alias will match the name.
216 216
 
217
-**--log-driver**="*json-file*|*syslog*|*journald*|*gelf*|*fluentd*|*awslogs*|*splunk*|*none*"
217
+**--log-driver**="*json-file*|*syslog*|*journald*|*gelf*|*fluentd*|*awslogs*|*splunk*|*etwlogs*|*none*"
218 218
   Logging driver for container. Default is defined by daemon `--log-driver` flag.
219 219
   **Warning**: the `docker logs` command works only for the `json-file` and
220 220
   `journald` logging drivers.
... ...
@@ -185,7 +185,7 @@ unix://[/path/to/socket] to use.
185 185
 **--label**="[]"
186 186
   Set key=value labels to the daemon (displayed in `docker info`)
187 187
 
188
-**--log-driver**="*json-file*|*syslog*|*journald*|*gelf*|*fluentd*|*awslogs*|*none*"
188
+**--log-driver**="*json-file*|*syslog*|*journald*|*gelf*|*fluentd*|*awslogs*|*splunk*|*etwlogs*|*none*"
189 189
   Default driver for container logs. Default is `json-file`.
190 190
   **Warning**: `docker logs` command works only for `json-file` logging driver.
191 191
 
... ...
@@ -320,7 +320,7 @@ container can access the exposed port via a private networking interface. Docker
320 320
 will set some environment variables in the client container to help indicate
321 321
 which interface and port to use.
322 322
 
323
-**--log-driver**="*json-file*|*syslog*|*journald*|*gelf*|*fluentd*|*awslogs*|*splunk*|*none*"
323
+**--log-driver**="*json-file*|*syslog*|*journald*|*gelf*|*fluentd*|*awslogs*|*splunk*|*etwlogs*|*none*"
324 324
   Logging driver for container. Default is defined by daemon `--log-driver` flag.
325 325
   **Warning**: the `docker logs` command works only for the `json-file` and
326 326
   `journald` logging drivers.