mirror of
				https://github.com/moby/moby.git
				synced 2022-11-09 12:21:53 -05:00 
			
		
		
		
	Windows: Add ETW logging driver plug-in
Signed-off-by: Cedric Davies <cedricda@microsoft.com>
This commit is contained in:
		
							parent
							
								
									46a61b7240
								
							
						
					
					
						commit
						3fe60bbf95
					
				
					 9 changed files with 268 additions and 4 deletions
				
			
		| 
						 | 
				
			
			@ -4,6 +4,7 @@ import (
 | 
			
		|||
	// Importing packages here only to make sure their init gets called and
 | 
			
		||||
	// therefore they register themselves to the logdriver factory.
 | 
			
		||||
	_ "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/splunk"
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										183
									
								
								daemon/logger/etwlogs/etwlogs_windows.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								daemon/logger/etwlogs/etwlogs_windows.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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
									
								
								docs/admin/logging/etwlogs.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								docs/admin/logging/etwlogs.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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.
 | 
			
		||||
| 
						 | 
				
			
			@ -20,3 +20,4 @@ weight=8
 | 
			
		|||
* [Journald logging driver](journald.md)
 | 
			
		||||
* [Amazon CloudWatch Logs logging driver](awslogs.md)
 | 
			
		||||
* [Splunk logging driver](splunk.md)
 | 
			
		||||
* [ETW logging driver](etwlogs.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).                                          |
 | 
			
		||||
| `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.                                 |
 | 
			
		||||
| `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`
 | 
			
		||||
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)
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -402,7 +402,7 @@ Json Parameters:
 | 
			
		|||
        systems, such as SELinux.
 | 
			
		||||
    -   **LogConfig** - Log configuration for the container, specified as a JSON object in the form
 | 
			
		||||
          `{ "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.
 | 
			
		||||
    -   **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.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -214,7 +214,7 @@ millions of trillions.
 | 
			
		|||
   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.
 | 
			
		||||
 | 
			
		||||
**--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.
 | 
			
		||||
  **Warning**: the `docker logs` command works only for the `json-file` and
 | 
			
		||||
  `journald` logging drivers.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -185,7 +185,7 @@ unix://[/path/to/socket] to use.
 | 
			
		|||
**--label**="[]"
 | 
			
		||||
  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`.
 | 
			
		||||
  **Warning**: `docker logs` command works only for `json-file` logging driver.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
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.
 | 
			
		||||
  **Warning**: the `docker logs` command works only for the `json-file` and
 | 
			
		||||
  `journald` logging drivers.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue