mirror of
https://github.com/hanzoai/telemetry.git
synced 2026-10-30 20:01:49 +00:00
#### Description Remove the dependency on the otelconftelemetry config struct when checking if logs should go to the Windows Event Log. Instead, we enable otelcol to intercept the zap.Config and dynamically inject options. #### Link to tracking issue Fixes #14002 #### Testing Covered by https://github.com/open-telemetry/opentelemetry-collector/blob/1da69f720810b0241b399d30b13f51f67acd7f64/otelcol/collector_windows_service_test.go#L27 #### Documentation N/A
214 lines
5.8 KiB
Go
214 lines
5.8 KiB
Go
// Copyright The OpenTelemetry Authors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
//go:build windows
|
|
|
|
package otelcol // import "go.opentelemetry.io/collector/otelcol"
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"slices"
|
|
"time"
|
|
|
|
"go.uber.org/zap"
|
|
"go.uber.org/zap/zapcore"
|
|
"golang.org/x/sys/windows/svc"
|
|
"golang.org/x/sys/windows/svc/eventlog"
|
|
|
|
"go.opentelemetry.io/collector/featuregate"
|
|
)
|
|
|
|
type windowsService struct {
|
|
settings CollectorSettings
|
|
col *Collector
|
|
flags *flag.FlagSet
|
|
}
|
|
|
|
// NewSvcHandler constructs a new svc.Handler using the given CollectorSettings.
|
|
func NewSvcHandler(set CollectorSettings) svc.Handler {
|
|
return &windowsService{settings: set, flags: flags(featuregate.GlobalRegistry())}
|
|
}
|
|
|
|
// Execute implements https://godoc.org/golang.org/x/sys/windows/svc#Handler
|
|
func (s *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, 1213 // 1213: ERROR_INVALID_SERVICENAME
|
|
}
|
|
|
|
elog, err := openEventLog(args[0])
|
|
if err != nil {
|
|
return false, 1501 // 1501: ERROR_EVENTLOG_CANT_START
|
|
}
|
|
|
|
colErrorChannel := make(chan error, 1)
|
|
|
|
changes <- svc.Status{State: svc.StartPending}
|
|
if err = s.start(elog, colErrorChannel); err != nil {
|
|
_ = elog.Error(3, fmt.Sprintf("failed to start service: %v", err))
|
|
return false, 1064 // 1064: 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}
|
|
if err = s.stop(colErrorChannel); err != nil {
|
|
_ = elog.Error(3, fmt.Sprintf("errors occurred while shutting down the service: %v", err))
|
|
}
|
|
changes <- svc.Status{State: svc.Stopped}
|
|
return false, 0
|
|
|
|
default:
|
|
_ = elog.Error(3, fmt.Sprintf("unexpected service control request #%d", req.Cmd))
|
|
return false, 1052 // 1052: ERROR_INVALID_SERVICE_CONTROL
|
|
}
|
|
}
|
|
|
|
return false, 0
|
|
}
|
|
|
|
func (s *windowsService) start(elog *eventlog.Log, colErrorChannel chan error) error {
|
|
// Parse all the flags manually.
|
|
if err := s.flags.Parse(os.Args[1:]); err != nil {
|
|
return err
|
|
}
|
|
|
|
var err error
|
|
err = updateSettingsUsingFlags(&s.settings, s.flags)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.col, err = NewCollector(s.settings)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Override the Zap logger to write to the Windows Event Log
|
|
// if no file output is specified.
|
|
s.col.buildZapLogger = func(cfg zap.Config, opts ...zap.Option) (*zap.Logger, error) {
|
|
for _, output := range cfg.OutputPaths {
|
|
if output != "stdout" && output != "stderr" {
|
|
// A file has been specified in the configuration,
|
|
// so do not use the Windows Event Log.
|
|
return cfg.Build(opts...)
|
|
}
|
|
}
|
|
opts = slices.Insert(opts, 0, zap.WrapCore(withWindowsCore(elog)))
|
|
return cfg.Build(opts...)
|
|
}
|
|
|
|
// col.Run blocks until receiving a SIGTERM signal, so needs to be started
|
|
// asynchronously, but it will exit early if an error occurs on startup
|
|
go func() {
|
|
colErrorChannel <- s.col.Run(context.Background())
|
|
}()
|
|
|
|
// wait until the collector server is in the Running state
|
|
go func() {
|
|
for {
|
|
state := s.col.GetState()
|
|
if state == StateRunning {
|
|
colErrorChannel <- nil
|
|
break
|
|
}
|
|
time.Sleep(time.Millisecond * 200)
|
|
}
|
|
}()
|
|
|
|
// wait until the collector server is in the Running state, or an error was returned
|
|
return <-colErrorChannel
|
|
}
|
|
|
|
func (s *windowsService) stop(colErrorChannel chan error) error {
|
|
s.col.Shutdown()
|
|
// return the response of col.Start
|
|
return <-colErrorChannel
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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()
|
|
}
|
|
|
|
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"
|
|
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
|
return windowsEventLogCore{core, elog, zapcore.NewConsoleEncoder(encoderConfig)}
|
|
}
|
|
}
|