1
0
Fork 0
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:
Cedric Davies 2016-01-25 14:49:52 -08:00
parent 46a61b7240
commit 3fe60bbf95
9 changed files with 268 additions and 4 deletions

View file

@ -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"
)

View 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
}

View 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.

View file

@ -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)

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.