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
|
// 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
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)
|
* [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)
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Add table
Reference in a new issue