Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add log output to Windows Event Log #5913

Merged
merged 2 commits into from
Dec 21, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a big fan of naming we have for historical reason and I'm thinking we should change it for 7.0. Perhaps we could already follow here a new scheme similar to what we have in other places:

logging.eventlog.enabled. Like this we can have specific logging options under the namespace.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am 100% on board with changing how logging is configured for 7.0. I did it this way to be consistent. I will open an issue to track things that I'd like to "break" for 7.0 (I have a list 😄 ).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be possible to already do the "new" scheme here as so far it's just a name. More thinking then we have to break one option less for 7 :-) But not sure if we are already settle on if that should be new scheme.

+1 on the issue.

Copy link
Member Author

@andrewkroh andrewkroh Dec 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well the scheme I had in mind kind of conflicts. I was thinking of something like.

logging.output:
- type: file
  level: info
  json: true
- type: eventlog
  level: warn

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it, then lets keep it as is in your PR and break it later so we don't need to come to a conclusion now.

I like your format as it gives max flexibility. I assume you have in mind that more then 1 log output can be enabled?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, more than one output and possibly more than one instance of an output type (not 100% sure yet; requires more thought).


# 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{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 on the cleanup

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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that change related to this PR?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not related. I just realized that there can be up to 4 tabs.


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