Forráskód Böngészése

Windows: Add ETW logging driver plug-in

Signed-off-by: Cedric Davies <cedricda@microsoft.com>
Cedric Davies 9 éve
szülő
commit
3fe60bbf95

+ 1 - 0
daemon/logdrivers_windows.go

@@ -4,6 +4,7 @@ import (
 	// Importing packages here only to make sure their init gets called and
 	// Importing packages here only to make sure their init gets called and
 	// therefore they register themselves to the logdriver factory.
 	// therefore they register themselves to the logdriver factory.
 	_ "github.com/docker/docker/daemon/logger/awslogs"
 	_ "github.com/docker/docker/daemon/logger/awslogs"
+	_ "github.com/docker/docker/daemon/logger/etwlogs"
 	_ "github.com/docker/docker/daemon/logger/jsonfilelog"
 	_ "github.com/docker/docker/daemon/logger/jsonfilelog"
 	_ "github.com/docker/docker/daemon/logger/splunk"
 	_ "github.com/docker/docker/daemon/logger/splunk"
 )
 )

+ 183 - 0
daemon/logger/etwlogs/etwlogs_windows.go

@@ -0,0 +1,183 @@
+// 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 a 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"
+)
+
+type etwLogs struct {
+	containerName string
+	imageName     string
+	containerID   string
+	imageID       string
+}
+
+const (
+	name             = "etwlogs"
+	win32CallSuccess = 0
+)
+
+var win32Lib *syscall.DLL
+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 win32Lib, err = syscall.LoadDLL("Advapi32.dll"); err != nil {
+			return err
+		}
+		if err = callEventRegister(); err != nil {
+			win32Lib.Release()
+			win32Lib = nil
+			return err
+		}
+	}
+
+	refCount++
+	return nil
+}
+
+func unregisterETWProvider() {
+	mu.Lock()
+	defer mu.Unlock()
+	if refCount == 1 {
+		if callEventUnregister() {
+			refCount--
+			providerHandle = syscall.InvalidHandle
+			win32Lib.Release()
+			win32Lib = nil
+		}
+		// Not returning an error if EventUnregister fails, because etwLogs will continue to work
+	} else {
+		refCount--
+	}
+}
+
+func callEventRegister() error {
+	proc, err := win32Lib.FindProc("EventRegister")
+	if err != nil {
+		return err
+	}
+	// 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, _, _ := proc.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 {
+	proc, err := win32Lib.FindProc("EventWriteString")
+	if err != nil {
+		return err
+	}
+	ret, _, _ := proc.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 {
+	proc, err := win32Lib.FindProc("EventUnregister")
+	if err != nil {
+		return false
+	}
+	ret, _, _ := proc.Call(uintptr(providerHandle))
+	if ret != win32CallSuccess {
+		return false
+	}
+	return true
+}

+ 69 - 0
docs/admin/logging/etwlogs.md

@@ -0,0 +1,69 @@
+<!--[metadata]>
++++
+title = "ETW logging driver"
+description = "Describes how to use the etwlogs logging driver."
+keywords = ["ETW, docker, logging, driver"]
+[menu.main]
+parent = "smn_logging" 
+weight=2
++++
+<![end-metadata]-->
+
+
+# ETW logging driver
+
+The ETW logging driver forwards container logs as ETW events. 
+ETW stands for Event Tracing in Windows, and is the common framework
+for tracing applications in Windows. Each ETW event contains a message
+with both the log and its context information. A client can then create
+an ETW listener to listen to these events. 
+
+The ETW provider that this logging driver registers with Windows, has the 
+GUID identifier of: `{a3693192-9ed6-46d2-a981-f8226c8363bd}`. A client creates an 
+ETW listener and registers to listen to events from the logging driver's provider. 
+It does not matter the order in which the provider and listener are created. 
+A client can create their ETW listener and start listening for events from the provider, 
+before the provider has been registered with the system. 
+
+## Usage
+
+Here is an example of how to listen to these events using the logman utility program 
+included in most installations of Windows:
+
+   1. `logman start -ets DockerContainerLogs -p {a3693192-9ed6-46d2-a981-f8226c8363bd} 0 0 -o trace.etl`
+   2. Run your container(s) with the etwlogs driver, by adding `--log-driver=etwlogs` 
+   to the Docker run command, and generate log messages.
+   3. `logman stop -ets DockerContainerLogs`
+   4. This will generate an etl file that contains the events. One way to convert this file into 
+   human-readable form is to run: `tracerpt -y trace.etl`. 
+   
+Each ETW event will contain a structured message string in this format:
+
+    container_name: %s, image_name: %s, container_id: %s, image_id: %s, source: [stdout | stderr], log: %s
+
+Details on each item in the message can be found below:
+
+| Field                | Description                                     |
+-----------------------|-------------------------------------------------|
+| `container_name`     | The container name at the time it was started.  |
+| `image_name`         | The name of the container's image.              |
+| `container_id`       | The full 64-character container ID.             |
+| `image_id`           | The full ID of the container's image.           |
+| `source`             | `stdout` or `stderr`.                           |
+| `log`                | The container log message.                      |
+
+Here is an example event message:
+
+    container_name: backstabbing_spence, 
+    image_name: windowsservercore, 
+    container_id: f14bb55aa862d7596b03a33251c1be7dbbec8056bbdead1da8ec5ecebbe29731, 
+    image_id: sha256:2f9e19bd998d3565b4f345ac9aaf6e3fc555406239a4fb1b1ba879673713824b, 
+    source: stdout, 
+    log: Hello world!
+
+A client can parse this message string to get both the log message, as well as its 
+context information. Note that the time stamp is also available within the ETW event. 
+
+**Note**  This ETW provider emits only a message string, and not a specially 
+structured ETW event. Therefore, it is not required to register a manifest file 
+with the system to read and interpret its ETW events.

+ 1 - 0
docs/admin/logging/index.md

@@ -20,3 +20,4 @@ weight=8
 * [Journald logging driver](journald.md)
 * [Journald logging driver](journald.md)
 * [Amazon CloudWatch Logs logging driver](awslogs.md)
 * [Amazon CloudWatch Logs logging driver](awslogs.md)
 * [Splunk logging driver](splunk.md)
 * [Splunk logging driver](splunk.md)
+* [ETW logging driver](etwlogs.md)

+ 10 - 0
docs/admin/logging/overview.md

@@ -26,6 +26,7 @@ container's logging driver. The following options are supported:
 | `fluentd`   | Fluentd logging driver for Docker. Writes log messages to `fluentd` (forward input).                                          |
 | `fluentd`   | Fluentd logging driver for Docker. Writes log messages to `fluentd` (forward input).                                          |
 | `awslogs`   | Amazon CloudWatch Logs logging driver for Docker. Writes log messages to Amazon CloudWatch Logs.                              |
 | `awslogs`   | Amazon CloudWatch Logs logging driver for Docker. Writes log messages to Amazon CloudWatch Logs.                              |
 | `splunk`    | Splunk logging driver for Docker. Writes log messages to `splunk` using HTTP Event Collector.                                 |
 | `splunk`    | Splunk logging driver for Docker. Writes log messages to `splunk` using HTTP Event Collector.                                 |
+| `etwlogs`   | ETW logging driver for Docker on Windows. Writes log messages as ETW events.                                                  |
 
 
 The `docker logs`command is available only for the `json-file` and `journald`
 The `docker logs`command is available only for the `json-file` and `journald`
 logging drivers.
 logging drivers.
@@ -204,3 +205,12 @@ The Splunk logging driver requires the following options:
 
 
 For detailed information about working with this logging driver, see the [Splunk logging driver](splunk.md)
 For detailed information about working with this logging driver, see the [Splunk logging driver](splunk.md)
 reference documentation.
 reference documentation.
+
+## ETW logging driver options
+
+The etwlogs logging driver does not require any options to be specified. This logging driver will forward each log message
+as an ETW event. An ETW listener can then be created to listen for these events. 
+
+For detailed information on working with this logging driver, see [the ETW logging driver](etwlogs.md) reference documentation.
+
+

+ 1 - 1
docs/reference/api/docker_remote_api_v1.23.md

@@ -402,7 +402,7 @@ Json Parameters:
         systems, such as SELinux.
         systems, such as SELinux.
     -   **LogConfig** - Log configuration for the container, specified as a JSON object in the form
     -   **LogConfig** - Log configuration for the container, specified as a JSON object in the form
           `{ "Type": "<driver_name>", "Config": {"key1": "val1"}}`.
           `{ "Type": "<driver_name>", "Config": {"key1": "val1"}}`.
-          Available types: `json-file`, `syslog`, `journald`, `gelf`, `awslogs`, `splunk`, `none`.
+          Available types: `json-file`, `syslog`, `journald`, `gelf`, `fluentd`, `awslogs`, `splunk`, `etwlogs`, `none`.
           `json-file` logging driver.
           `json-file` logging driver.
     -   **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.
     -   **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.
     -   **VolumeDriver** - Driver that this container users to mount volumes.
     -   **VolumeDriver** - Driver that this container users to mount volumes.

+ 1 - 1
man/docker-create.1.md

@@ -214,7 +214,7 @@ millions of trillions.
    Add link to another container in the form of <name or id>:alias or just
    Add link to another container in the form of <name or id>:alias or just
    <name or id> in which case the alias will match the name.
    <name or id> in which case the alias will match the name.
 
 
-**--log-driver**="*json-file*|*syslog*|*journald*|*gelf*|*fluentd*|*awslogs*|*splunk*|*none*"
+**--log-driver**="*json-file*|*syslog*|*journald*|*gelf*|*fluentd*|*awslogs*|*splunk*|*etwlogs*|*none*"
   Logging driver for container. Default is defined by daemon `--log-driver` flag.
   Logging driver for container. Default is defined by daemon `--log-driver` flag.
   **Warning**: the `docker logs` command works only for the `json-file` and
   **Warning**: the `docker logs` command works only for the `json-file` and
   `journald` logging drivers.
   `journald` logging drivers.

+ 1 - 1
man/docker-daemon.8.md

@@ -185,7 +185,7 @@ unix://[/path/to/socket] to use.
 **--label**="[]"
 **--label**="[]"
   Set key=value labels to the daemon (displayed in `docker info`)
   Set key=value labels to the daemon (displayed in `docker info`)
 
 
-**--log-driver**="*json-file*|*syslog*|*journald*|*gelf*|*fluentd*|*awslogs*|*none*"
+**--log-driver**="*json-file*|*syslog*|*journald*|*gelf*|*fluentd*|*awslogs*|*splunk*|*etwlogs*|*none*"
   Default driver for container logs. Default is `json-file`.
   Default driver for container logs. Default is `json-file`.
   **Warning**: `docker logs` command works only for `json-file` logging driver.
   **Warning**: `docker logs` command works only for `json-file` logging driver.
 
 

+ 1 - 1
man/docker-run.1.md

@@ -320,7 +320,7 @@ container can access the exposed port via a private networking interface. Docker
 will set some environment variables in the client container to help indicate
 will set some environment variables in the client container to help indicate
 which interface and port to use.
 which interface and port to use.
 
 
-**--log-driver**="*json-file*|*syslog*|*journald*|*gelf*|*fluentd*|*awslogs*|*splunk*|*none*"
+**--log-driver**="*json-file*|*syslog*|*journald*|*gelf*|*fluentd*|*awslogs*|*splunk*|*etwlogs*|*none*"
   Logging driver for container. Default is defined by daemon `--log-driver` flag.
   Logging driver for container. Default is defined by daemon `--log-driver` flag.
   **Warning**: the `docker logs` command works only for the `json-file` and
   **Warning**: the `docker logs` command works only for the `json-file` and
   `journald` logging drivers.
   `journald` logging drivers.