cmd/opampsupervisor/supervisor/supervisor_windows.go (128 lines of code) (raw):
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
//go:build windows
package supervisor
import (
"flag"
"fmt"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/eventlog"
"github.com/open-telemetry/opentelemetry-collector-contrib/cmd/opampsupervisor/supervisor/config"
)
type windowsService struct {
sup *Supervisor
}
func NewSvcHandler() svc.Handler {
return &windowsService{}
}
func (ws *windowsService) Execute(args []string, requests <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) {
// The first argument supplied to service.Execute is the service name. If this is
// not provided for some reason, raise a relevant error to the system event log
if len(args) == 0 {
return false, uint32(windows.ERROR_INVALID_SERVICENAME)
}
elog, err := openEventLog(args[0])
if err != nil {
return false, uint32(windows.ERROR_EVENTLOG_CANT_START)
}
changes <- svc.Status{State: svc.StartPending}
if err = ws.start(elog); err != nil {
_ = elog.Error(3, fmt.Sprintf("failed to start service: %v", err))
return false, uint32(windows.ERROR_EXCEPTION_IN_SERVICE)
}
changes <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown}
for req := range requests {
switch req.Cmd {
case svc.Interrogate:
changes <- req.CurrentStatus
case svc.Stop, svc.Shutdown:
changes <- svc.Status{State: svc.StopPending}
ws.stop()
changes <- svc.Status{State: svc.Stopped}
return false, 0
default:
_ = elog.Error(3, fmt.Sprintf("unexpected service control request #%d", req.Cmd))
return false, uint32(windows.ERROR_INVALID_SERVICE_CONTROL)
}
}
return false, 0
}
func (ws *windowsService) start(elog *eventlog.Log) error {
configFlag := flag.String("config", "", "Path to a supervisor configuration file")
flag.Parse()
logger, _ := zap.NewDevelopment(zap.WrapCore(withWindowsCore(elog)))
cfg, err := config.Load(*configFlag)
if err != nil {
return fmt.Errorf("load config: %w", err)
}
sup, err := NewSupervisor(logger, cfg)
if err != nil {
return fmt.Errorf("new supervisor: %w", err)
}
ws.sup = sup
return ws.sup.Start()
}
func (ws *windowsService) stop() {
ws.sup.Shutdown()
}
func openEventLog(serviceName string) (*eventlog.Log, error) {
elog, err := eventlog.Open(serviceName)
if err != nil {
return nil, fmt.Errorf("service failed to open event log: %w", err)
}
return elog, nil
}
// Logger wrappings
var _ zapcore.Core = (*windowsEventLogCore)(nil)
type windowsEventLogCore struct {
core zapcore.Core
elog *eventlog.Log
encoder zapcore.Encoder
}
func (w windowsEventLogCore) Enabled(level zapcore.Level) bool {
return w.core.Enabled(level)
}
func (w windowsEventLogCore) With(fields []zapcore.Field) zapcore.Core {
enc := w.encoder.Clone()
for _, field := range fields {
field.AddTo(enc)
}
return windowsEventLogCore{
core: w.core,
elog: w.elog,
encoder: enc,
}
}
func (w windowsEventLogCore) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
if w.Enabled(ent.Level) {
return ce.AddCore(ent, w)
}
return ce
}
func (w windowsEventLogCore) Write(ent zapcore.Entry, fields []zapcore.Field) error {
buf, err := w.encoder.EncodeEntry(ent, fields)
if err != nil {
_ = w.elog.Warning(2, fmt.Sprintf("failed encoding log entry %v\r\n", err))
return err
}
msg := buf.String()
buf.Free()
switch ent.Level {
case zapcore.FatalLevel, zapcore.PanicLevel, zapcore.DPanicLevel:
// golang.org/x/sys/windows/svc/eventlog does not support Critical level event logs
return w.elog.Error(3, msg)
case zapcore.ErrorLevel:
return w.elog.Error(3, msg)
case zapcore.WarnLevel:
return w.elog.Warning(2, msg)
case zapcore.InfoLevel:
return w.elog.Info(1, msg)
}
// We would not be here if debug were disabled so log as info to not drop.
return w.elog.Info(1, msg)
}
func (w windowsEventLogCore) Sync() error {
return w.core.Sync()
}
// TODO: If supervisor logging becomes configurable, update this function to respect that config
func withWindowsCore(elog *eventlog.Log) func(zapcore.Core) zapcore.Core {
return func(core zapcore.Core) zapcore.Core {
// Use the Windows Event Log
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.LineEnding = "\r\n"
return windowsEventLogCore{core, elog, zapcore.NewConsoleEncoder(encoderConfig)}
}
}