blob: de128a2e93d078260e5d6cf4b7e1ceed7373b3c3 [file] [log] [blame]
// 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
}