github.com/rawahars/moby@v24.0.4+incompatible/daemon/logger/etwlogs/etwlogs_windows.go (about)

     1  // Package etwlogs provides a log driver for forwarding container logs
     2  // as ETW events.(ETW stands for Event Tracing for Windows)
     3  // A client can then create an ETW listener to listen for events that are sent
     4  // by the ETW provider that we register, using the provider's GUID "a3693192-9ed6-46d2-a981-f8226c8363bd".
     5  // Here is an example of how to do this using the logman utility:
     6  // 1. logman start -ets DockerContainerLogs -p {a3693192-9ed6-46d2-a981-f8226c8363bd} 0 0 -o trace.etl
     7  // 2. Run container(s) and generate log messages
     8  // 3. logman stop -ets DockerContainerLogs
     9  // 4. You can then convert the etl log file to XML using: tracerpt -y trace.etl
    10  //
    11  // Each container log message generates an ETW event that also contains:
    12  // the container name and ID, the timestamp, and the stream type.
    13  package etwlogs // import "github.com/docker/docker/daemon/logger/etwlogs"
    14  
    15  import (
    16  	"fmt"
    17  	"sync"
    18  	"unsafe"
    19  
    20  	"github.com/Microsoft/go-winio/pkg/etw"
    21  	"github.com/Microsoft/go-winio/pkg/guid"
    22  	"github.com/docker/docker/daemon/logger"
    23  	"github.com/sirupsen/logrus"
    24  	"golang.org/x/sys/windows"
    25  )
    26  
    27  type etwLogs struct {
    28  	containerName string
    29  	imageName     string
    30  	containerID   string
    31  	imageID       string
    32  }
    33  
    34  const (
    35  	name             = "etwlogs"
    36  	providerGUID     = `a3693192-9ed6-46d2-a981-f8226c8363bd`
    37  	win32CallSuccess = 0
    38  )
    39  
    40  var (
    41  	modAdvapi32          = windows.NewLazySystemDLL("Advapi32.dll")
    42  	procEventWriteString = modAdvapi32.NewProc("EventWriteString")
    43  )
    44  
    45  var (
    46  	providerHandle windows.Handle
    47  	mu             sync.Mutex
    48  	refCount       int
    49  	provider       *etw.Provider
    50  )
    51  
    52  func init() {
    53  	providerHandle = windows.InvalidHandle
    54  	if err := logger.RegisterLogDriver(name, New); err != nil {
    55  		panic(err)
    56  	}
    57  }
    58  
    59  // New creates a new etwLogs logger for the given container and registers the EWT provider.
    60  func New(info logger.Info) (logger.Logger, error) {
    61  	if err := registerETWProvider(); err != nil {
    62  		return nil, err
    63  	}
    64  	logrus.Debugf("logging driver etwLogs configured for container: %s.", info.ContainerID)
    65  
    66  	return &etwLogs{
    67  		containerName: info.Name(),
    68  		imageName:     info.ContainerImageName,
    69  		containerID:   info.ContainerID,
    70  		imageID:       info.ContainerImageID,
    71  	}, nil
    72  }
    73  
    74  // Log logs the message to the ETW stream.
    75  func (etwLogger *etwLogs) Log(msg *logger.Message) error {
    76  	// TODO(thaJeztah): log structured events instead and use provider.WriteEvent().
    77  	m := createLogMessage(etwLogger, msg)
    78  	logger.PutMessage(msg)
    79  	return callEventWriteString(m)
    80  }
    81  
    82  // Close closes the logger by unregistering the ETW provider.
    83  func (etwLogger *etwLogs) Close() error {
    84  	unregisterETWProvider()
    85  	return nil
    86  }
    87  
    88  func (etwLogger *etwLogs) Name() string {
    89  	return name
    90  }
    91  
    92  func createLogMessage(etwLogger *etwLogs, msg *logger.Message) string {
    93  	return fmt.Sprintf("container_name: %s, image_name: %s, container_id: %s, image_id: %s, source: %s, log: %s",
    94  		etwLogger.containerName,
    95  		etwLogger.imageName,
    96  		etwLogger.containerID,
    97  		etwLogger.imageID,
    98  		msg.Source,
    99  		msg.Line)
   100  }
   101  
   102  func registerETWProvider() error {
   103  	mu.Lock()
   104  	defer mu.Unlock()
   105  	if refCount == 0 {
   106  		var err error
   107  		provider, err = callEventRegister()
   108  		if err != nil {
   109  			return err
   110  		}
   111  	}
   112  
   113  	refCount++
   114  	return nil
   115  }
   116  
   117  func unregisterETWProvider() {
   118  	mu.Lock()
   119  	defer mu.Unlock()
   120  	if refCount == 1 {
   121  		if err := callEventUnregister(); err != nil {
   122  			// Not returning an error if EventUnregister fails, because etwLogs will continue to work
   123  			return
   124  		}
   125  		refCount--
   126  		provider = nil
   127  		providerHandle = windows.InvalidHandle
   128  	} else {
   129  		refCount--
   130  	}
   131  }
   132  
   133  func callEventRegister() (*etw.Provider, error) {
   134  	providerID, _ := guid.FromString(providerGUID)
   135  	p, err := etw.NewProviderWithOptions("", etw.WithID(providerID))
   136  	if err != nil {
   137  		logrus.WithError(err).Error("Failed to register ETW provider")
   138  		return nil, fmt.Errorf("failed to register ETW provider: %v", err)
   139  	}
   140  	return p, nil
   141  }
   142  
   143  // TODO(thaJeztah): port this function to github.com/Microsoft/go-winio/pkg/etw.
   144  func callEventWriteString(message string) error {
   145  	utf16message, err := windows.UTF16FromString(message)
   146  	if err != nil {
   147  		return err
   148  	}
   149  
   150  	ret, _, _ := procEventWriteString.Call(uintptr(providerHandle), 0, 0, uintptr(unsafe.Pointer(&utf16message[0])))
   151  	if ret != win32CallSuccess {
   152  		logrus.WithError(err).Error("ETWLogs provider failed to log message")
   153  		return fmt.Errorf("ETWLogs provider failed to log message: %v", err)
   154  	}
   155  	return nil
   156  }
   157  
   158  func callEventUnregister() error {
   159  	return provider.Close()
   160  }