Skip to content

Commit

Permalink
Add log output to Windows Event Log
Browse files Browse the repository at this point in the history
By setting `logging.to_eventlog: true` all log output will be written
to the Application log. The source name will be name of the Beat.
  • Loading branch information
andrewkroh authored and ruflin committed Dec 21, 2017
1 parent c027131 commit 6244703
Show file tree
Hide file tree
Showing 24 changed files with 197 additions and 73 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ https://github.com/elastic/beats/compare/v6.0.0-beta2...master[Check the HEAD di
- Add the ability to write structured logs. {pull}5901[5901]
- Use structured logging for the metrics that are periodically logged via the
`logging.metrics` feature. {pull}5915[5915]
- Add the ability to log to the Windows Event Log. {pull}5913[5813]

*Auditbeat*

Expand Down
12 changes: 7 additions & 5 deletions auditbeat/auditbeat.reference.yml
Original file line number Diff line number Diff line change
Expand Up @@ -831,12 +831,11 @@ setup.kibana:


#================================ Logging ======================================
# There are three options for the log output: syslog, file, stderr.
# Under Windows systems, the log files are per default sent to the file output,
# under all other system per default to syslog.
# There are four options for the log output: file, stderr, syslog, eventlog
# The file output is the default.

# Sets log level. The default log level is info.
# Available log levels are: critical, error, warning, info, debug
# Available log levels are: error, warning, info, debug
#logging.level: info

# Enable debug output for selected components. To enable all selectors use ["*"]
Expand All @@ -845,7 +844,10 @@ setup.kibana:
#logging.selectors: [ ]

# Send all logging output to syslog. The default is false.
#logging.to_syslog: true
#logging.to_syslog: false

# Send all logging output to Windows Event Logs. The default is false.
#logging.to_eventlog: false

# If enabled, auditbeat periodically logs its internal metrics that have changed
# in the last period. For each metric that changed, the delta from the value at
Expand Down
2 changes: 1 addition & 1 deletion auditbeat/auditbeat.yml
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ output.elasticsearch:
#================================ Logging =====================================

# Sets log level. The default log level is info.
# Available log levels are: critical, error, warning, info, debug
# Available log levels are: error, warning, info, debug
#logging.level: debug

# At debug level, you can selectively enable logging only for some components.
Expand Down
12 changes: 7 additions & 5 deletions filebeat/filebeat.reference.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1266,12 +1266,11 @@ setup.kibana:


#================================ Logging ======================================
# There are three options for the log output: syslog, file, stderr.
# Under Windows systems, the log files are per default sent to the file output,
# under all other system per default to syslog.
# There are four options for the log output: file, stderr, syslog, eventlog
# The file output is the default.

# Sets log level. The default log level is info.
# Available log levels are: critical, error, warning, info, debug
# Available log levels are: error, warning, info, debug
#logging.level: info

# Enable debug output for selected components. To enable all selectors use ["*"]
Expand All @@ -1280,7 +1279,10 @@ setup.kibana:
#logging.selectors: [ ]

# Send all logging output to syslog. The default is false.
#logging.to_syslog: true
#logging.to_syslog: false

# Send all logging output to Windows Event Logs. The default is false.
#logging.to_eventlog: false

# If enabled, filebeat periodically logs its internal metrics that have changed
# in the last period. For each metric that changed, the delta from the value at
Expand Down
2 changes: 1 addition & 1 deletion filebeat/filebeat.yml
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ output.elasticsearch:
#================================ Logging =====================================

# Sets log level. The default log level is info.
# Available log levels are: critical, error, warning, info, debug
# Available log levels are: error, warning, info, debug
#logging.level: debug

# At debug level, you can selectively enable logging only for some components.
Expand Down
12 changes: 7 additions & 5 deletions heartbeat/heartbeat.reference.yml
Original file line number Diff line number Diff line change
Expand Up @@ -941,12 +941,11 @@ setup.kibana:


#================================ Logging ======================================
# There are three options for the log output: syslog, file, stderr.
# Under Windows systems, the log files are per default sent to the file output,
# under all other system per default to syslog.
# There are four options for the log output: file, stderr, syslog, eventlog
# The file output is the default.

# Sets log level. The default log level is info.
# Available log levels are: critical, error, warning, info, debug
# Available log levels are: error, warning, info, debug
#logging.level: info

# Enable debug output for selected components. To enable all selectors use ["*"]
Expand All @@ -955,7 +954,10 @@ setup.kibana:
#logging.selectors: [ ]

# Send all logging output to syslog. The default is false.
#logging.to_syslog: true
#logging.to_syslog: false

# Send all logging output to Windows Event Logs. The default is false.
#logging.to_eventlog: false

# If enabled, heartbeat periodically logs its internal metrics that have changed
# in the last period. For each metric that changed, the delta from the value at
Expand Down
2 changes: 1 addition & 1 deletion heartbeat/heartbeat.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ output.elasticsearch:
#================================ Logging =====================================

# Sets log level. The default log level is info.
# Available log levels are: critical, error, warning, info, debug
# Available log levels are: error, warning, info, debug
#logging.level: debug

# At debug level, you can selectively enable logging only for some components.
Expand Down
12 changes: 7 additions & 5 deletions libbeat/_meta/config.reference.yml
Original file line number Diff line number Diff line change
Expand Up @@ -727,12 +727,11 @@ setup.kibana:


#================================ Logging ======================================
# There are three options for the log output: syslog, file, stderr.
# Under Windows systems, the log files are per default sent to the file output,
# under all other system per default to syslog.
# There are four options for the log output: file, stderr, syslog, eventlog
# The file output is the default.

# Sets log level. The default log level is info.
# Available log levels are: critical, error, warning, info, debug
# Available log levels are: error, warning, info, debug
#logging.level: info

# Enable debug output for selected components. To enable all selectors use ["*"]
Expand All @@ -741,7 +740,10 @@ setup.kibana:
#logging.selectors: [ ]

# Send all logging output to syslog. The default is false.
#logging.to_syslog: true
#logging.to_syslog: false

# Send all logging output to Windows Event Logs. The default is false.
#logging.to_eventlog: false

# If enabled, beatname periodically logs its internal metrics that have changed
# in the last period. For each metric that changed, the delta from the value at
Expand Down
2 changes: 1 addition & 1 deletion libbeat/_meta/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ output.elasticsearch:
#================================ Logging =====================================

# Sets log level. The default log level is info.
# Available log levels are: critical, error, warning, info, debug
# Available log levels are: error, warning, info, debug
#logging.level: debug

# At debug level, you can selectively enable logging only for some components.
Expand Down
5 changes: 5 additions & 0 deletions libbeat/docs/loggingconfig.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ You can specify the following options in the `logging` section of the

When true, writes all logging output to the syslog.

[float]
==== `logging.to_eventlog`

When true, writes all logging output to the Windows Event Log.

[float]
==== `logging.to_files`

Expand Down
1 change: 1 addition & 0 deletions libbeat/logp/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type Config struct {
ToStderr bool `config:"to_stderr"`
ToSyslog bool `config:"to_syslog"`
ToFiles bool `config:"to_files"`
ToEventLog bool `config:"to_eventlog"`

Files FileConfig `config:"files"`

Expand Down
6 changes: 6 additions & 0 deletions libbeat/logp/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ func Configure(cfg Config) error {
sink, err = makeStderrOutput(cfg)
case cfg.ToSyslog:
sink, err = makeSyslogOutput(cfg)
case cfg.ToEventLog:
sink, err = makeEventLogOutput(cfg)
case cfg.ToFiles:
fallthrough
default:
Expand Down Expand Up @@ -158,6 +160,10 @@ func makeSyslogOutput(cfg Config) (zapcore.Core, error) {
return newSyslog(buildEncoder(cfg), cfg.Level.zapLevel())
}

func makeEventLogOutput(cfg Config) (zapcore.Core, error) {
return newEventLog(cfg.Beat, buildEncoder(cfg), cfg.Level.zapLevel())
}

func makeFileOutput(cfg Config) (zapcore.Core, error) {
name := cfg.Beat
if cfg.Files.Name != "" {
Expand Down
48 changes: 20 additions & 28 deletions libbeat/logp/encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@ import (
"go.uber.org/zap/zapcore"
)

var baseEncodingConfig = zapcore.EncoderConfig{
TimeKey: "timestamp",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
MessageKey: "message",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.LowercaseLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeDuration: millisecondsDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
EncodeName: zapcore.FullNameEncoder,
}

func buildEncoder(cfg Config) zapcore.Encoder {
if cfg.JSON {
return zapcore.NewJSONEncoder(jsonEncoderConfig())
Expand All @@ -17,37 +32,14 @@ func buildEncoder(cfg Config) zapcore.Encoder {
}

func jsonEncoderConfig() zapcore.EncoderConfig {
return zapcore.EncoderConfig{
TimeKey: "timestamp",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
MessageKey: "message",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.LowercaseLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeDuration: millisecondsDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
EncodeName: zapcore.FullNameEncoder,
}
return baseEncodingConfig
}

func consoleEncoderConfig() zapcore.EncoderConfig {
return zapcore.EncoderConfig{
TimeKey: "timestamp",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
MessageKey: "message",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.CapitalLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeDuration: millisecondsDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
EncodeName: bracketedNameEncoder,
}
c := baseEncodingConfig
c.EncodeLevel = zapcore.CapitalLevelEncoder
c.EncodeName = bracketedNameEncoder
return c
}

func syslogEncoderConfig() zapcore.EncoderConfig {
Expand Down
12 changes: 12 additions & 0 deletions libbeat/logp/eventlog_unsupported.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// +build !windows

package logp

import (
"github.com/pkg/errors"
"go.uber.org/zap/zapcore"
)

func newEventLog(beatname string, encoder zapcore.Encoder, enab zapcore.LevelEnabler) (zapcore.Core, error) {
return nil, errors.New("eventlog is only supported on Windows")
}
92 changes: 92 additions & 0 deletions libbeat/logp/eventlog_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package logp

import (
"strings"

"github.com/pkg/errors"
"go.uber.org/zap/zapcore"
"golang.org/x/sys/windows/svc/eventlog"
)

const (
// eventID is arbitrary but must be between [1-1000].
eventID = 100
supports = eventlog.Error | eventlog.Warning | eventlog.Info
)

const alreadyExistsMsg = "registry key already exists"

type eventLogCore struct {
zapcore.LevelEnabler
encoder zapcore.Encoder
fields []zapcore.Field
log *eventlog.Log
}

func newEventLog(appName string, encoder zapcore.Encoder, enab zapcore.LevelEnabler) (zapcore.Core, error) {
if appName == "" {
return nil, errors.New("appName cannot be empty")
}
appName = strings.Title(strings.ToLower(appName))

if err := eventlog.InstallAsEventCreate(appName, supports); err != nil {
if !strings.Contains(err.Error(), alreadyExistsMsg) {
return nil, errors.Wrap(err, "failed to setup eventlog")
}
}

log, err := eventlog.Open(appName)
if err != nil {
return nil, errors.Wrap(err, "failed to open eventlog")
}

return &eventLogCore{
LevelEnabler: enab,
encoder: encoder,
log: log,
}, nil
}

func (c *eventLogCore) With(fields []zapcore.Field) zapcore.Core {
clone := c.Clone()
clone.fields = append(clone.fields, fields...)
return clone
}

func (c *eventLogCore) Check(entry zapcore.Entry, checked *zapcore.CheckedEntry) *zapcore.CheckedEntry {
if c.Enabled(entry.Level) {
return checked.AddCore(entry, c)
}
return checked
}

func (c *eventLogCore) Write(entry zapcore.Entry, fields []zapcore.Field) error {
buffer, err := c.encoder.EncodeEntry(entry, fields)
if err != nil {
return errors.Wrap(err, "failed to encode entry")
}

msg := buffer.String()
switch entry.Level {
case zapcore.DebugLevel, zapcore.InfoLevel:
return c.log.Info(eventID, msg)
case zapcore.WarnLevel:
return c.log.Warning(eventID, msg)
case zapcore.ErrorLevel, zapcore.DPanicLevel, zapcore.PanicLevel, zapcore.FatalLevel:
return c.log.Error(eventID, msg)
default:
return errors.Errorf("unhandled log level: %v", entry.Level)
}
}

func (c *eventLogCore) Sync() error {
return nil
}

func (c *eventLogCore) Clone() *eventLogCore {
clone := *c
clone.encoder = c.encoder.Clone()
clone.fields = make([]zapcore.Field, len(c.fields), len(c.fields)+10)
copy(clone.fields, c.fields)
return &clone
}
2 changes: 1 addition & 1 deletion libbeat/logp/fields.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ var (
Int64s = zap.Int64s
Namespace = zap.Namespace
Reflect = zap.Reflect
Stack = zap.Reflect
Stack = zap.Stack
String = zap.String
Stringer = zap.Stringer
Strings = zap.Strings
Expand Down
2 changes: 1 addition & 1 deletion libbeat/logp/syslog_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func (c *syslogCore) Write(entry zapcore.Entry, fields []zapcore.Field) error {
}

// Console encoder writes tabs which don't render nicely with syslog.
replaceTabsWithSpaces(buffer.Bytes(), 2)
replaceTabsWithSpaces(buffer.Bytes(), 4)

msg := buffer.String()
switch entry.Level {
Expand Down
3 changes: 2 additions & 1 deletion libbeat/logp/syslog_unsupported.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
package logp

import (
"github.com/pkg/errors"
"go.uber.org/zap/zapcore"
)

func newSyslog(_ zapcore.Encoder, _ zapcore.LevelEnabler) (zapcore.Core, error) {
return zapcore.NewNopCore(), nil
return nil, errors.New("syslog is not supported on this OS")
}
Loading

0 comments on commit 6244703

Please sign in to comment.