Skip to content

Commit

Permalink
Implement windows event logging
Browse files Browse the repository at this point in the history
  • Loading branch information
Colin Sullivan committed Dec 29, 2016
1 parent bc5f864 commit 1df5af0
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 11 deletions.
60 changes: 50 additions & 10 deletions logger/syslog_windows.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,38 @@
// Copyright 2012-2014 Apcera Inc. All rights reserved.
// Copyright 2012-2016 Apcera Inc. All rights reserved.

// Package logger logs to the windows event log
package logger

import (
"fmt"
"log"
"golang.org/x/sys/windows/svc/eventlog"
"os"
"strings"
)

const (
natsEventSource = "NATS-Server"
)

// SysLogger logs to the windows event logger
type SysLogger struct {
writer *log.Logger
writer *eventlog.Log
debug bool
trace bool
}

// NewSysLogger creates a log using the windows event logger
func NewSysLogger(debug, trace bool) *SysLogger {
w := log.New(os.Stdout, "gnatsd", log.LstdFlags)
if err := eventlog.InstallAsEventCreate(natsEventSource, eventlog.Info|eventlog.Error|eventlog.Warning); err != nil {
if !strings.Contains(err.Error(), "registry key already exists") {
panic(fmt.Sprintf("could not access event log: %v", err))
}
}

w, err := eventlog.Open(natsEventSource)
if err != nil {
panic(fmt.Sprintf("could not open event log: %v", err))
}

return &SysLogger{
writer: w,
Expand All @@ -23,30 +41,52 @@ func NewSysLogger(debug, trace bool) *SysLogger {
}
}

// NewRemoteSysLogger creates a remote event logger
func NewRemoteSysLogger(fqn string, debug, trace bool) *SysLogger {
return NewSysLogger(debug, trace)
w, err := eventlog.OpenRemote(fqn, natsEventSource)
if err != nil {
panic(fmt.Sprintf("could not open event log: %v", err))
}

return &SysLogger{
writer: w,
debug: debug,
trace: trace,
}
}

func formatMsg(tag, format string, v ...interface{}) string {
orig := fmt.Sprintf(format, v...)
return fmt.Sprintf("pid[%d][%s]: %s", os.Getpid(), tag, orig)
}

// Noticef logs a notice statement
func (l *SysLogger) Noticef(format string, v ...interface{}) {
l.writer.Println("NOTICE", fmt.Sprintf(format, v...))
l.writer.Info(1, formatMsg("NOTICE", format, v...))
}

// Fatalf logs a fatal error
func (l *SysLogger) Fatalf(format string, v ...interface{}) {
l.writer.Println("CRITICAL", fmt.Sprintf(format, v...))
msg := formatMsg("FATAL", format, v...)
l.writer.Error(5, msg)
panic(msg)
}

// Errorf logs an error statement
func (l *SysLogger) Errorf(format string, v ...interface{}) {
l.writer.Println("ERROR", fmt.Sprintf(format, v...))
l.writer.Error(2, formatMsg("ERROR", format, v...))
}

// Debugf logs a debug statement
func (l *SysLogger) Debugf(format string, v ...interface{}) {
if l.debug {
l.writer.Println("DEBUG", fmt.Sprintf(format, v...))
l.writer.Info(3, formatMsg("DEBUG", format, v...))
}
}

// Tracef logs a trace statement
func (l *SysLogger) Tracef(format string, v ...interface{}) {
if l.trace {
l.writer.Println("NOTICE", fmt.Sprintf(format, v...))
l.writer.Info(4, formatMsg("TRACE", format, v...))
}
}
126 changes: 126 additions & 0 deletions logger/syslog_windows_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Copyright 2016 Apcera Inc. All rights reserved.
// +build windows

package logger

import (
"golang.org/x/sys/windows/svc/eventlog"
"os/exec"
"strings"
"testing"
)

// Skips testing if we do not have privledges to run this test.
// This lets us skip the tests for general (non admin/system) users.
func checkPrivledges(t *testing.T) {
src := "NATS-eventlog-testsource"
defer eventlog.Remove(src)
if err := eventlog.InstallAsEventCreate(src, eventlog.Info|eventlog.Error|eventlog.Warning); err != nil {
if strings.Contains(err.Error(), "Access is denied") {
t.Skip("skipping: elevated privledges are required.")
}
// let the tests report other types of errors
}
}

// lastLogEntryContains reads the last entry (/c:1 /rd:true) written
// to the event log by the NATS-Server source, returning true if the
// passed text was found, false otherwise.
func lastLogEntryContains(t *testing.T, text string) bool {
var output []byte
var err error

cmd := exec.Command("wevtutil.exe", "qe", "Application", "/q:*[System[Provider[@Name='NATS-Server']]]",
"/rd:true", "/c:1")
if output, err = cmd.Output(); err != nil {
t.Fatalf("Unable to execute command: %v", err)
}
return strings.Contains(string(output), text)
}

// TestSysLogger tests event logging on windows
func TestSysLogger(t *testing.T) {
checkPrivledges(t)
logger := NewSysLogger(false, false)
if logger.debug {
t.Fatalf("Expected %t, received %t\n", false, logger.debug)
}

if logger.trace {
t.Fatalf("Expected %t, received %t\n", false, logger.trace)
}
logger.Noticef("%s", "Noticef")
if !lastLogEntryContains(t, "[NOTICE]: Noticef") {
t.Fatalf("missing log entry")
}

logger.Errorf("%s", "Errorf")
if !lastLogEntryContains(t, "[ERROR]: Errorf") {
t.Fatalf("missing log entry")
}

logger.Tracef("%s", "Tracef")
if lastLogEntryContains(t, "Tracef") {
t.Fatalf("should not contain log entry")
}

logger.Debugf("%s", "Debugf")
if lastLogEntryContains(t, "Debugf") {
t.Fatalf("should not contain log entry")
}
}

// TestSysLoggerWithDebugAndTrace tests event logging
func TestSysLoggerWithDebugAndTrace(t *testing.T) {
checkPrivledges(t)
logger := NewSysLogger(true, true)
if !logger.debug {
t.Fatalf("Expected %t, received %t\n", true, logger.debug)
}

if !logger.trace {
t.Fatalf("Expected %t, received %t\n", true, logger.trace)
}

logger.Tracef("%s", "Tracef")
if !lastLogEntryContains(t, "[TRACE]: Tracef") {
t.Fatalf("missing log entry")
}

logger.Debugf("%s", "Debugf")
if !lastLogEntryContains(t, "[DEBUG]: Debugf") {
t.Fatalf("missing log entry")
}
}

// TestSysLoggerWithDebugAndTrace tests remote event logging
func TestRemoteSysLoggerWithDebugAndTrace(t *testing.T) {
checkPrivledges(t)
logger := NewRemoteSysLogger("127.0.0.1", true, true)
if !logger.debug {
t.Fatalf("Expected %t, received %t\n", true, logger.debug)
}

if !logger.trace {
t.Fatalf("Expected %t, received %t\n", true, logger.trace)
}
logger.Tracef("NATS %s", "[TRACE]: Remote Noticef")
if !lastLogEntryContains(t, "Remote Noticef") {
t.Fatalf("missing log entry")
}
}

func TestSysLoggerFatalf(t *testing.T) {
defer func() {
if r := recover(); r != nil {
if !lastLogEntryContains(t, "[FATAL]: Fatalf") {
t.Fatalf("missing log entry")
}
}
}()

checkPrivledges(t)
logger := NewSysLogger(true, true)
logger.Fatalf("%s", "Fatalf")
t.Fatalf("did not panic when expected to")
}
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Server Options:
Logging Options:
-l, --log <file> File to redirect log output
-T, --logtime Timestamp log entries (default: true)
-s, --syslog Enable syslog as log method
-s, --syslog Log to syslog or windows event log
-r, --remote_syslog <addr> Syslog server addr (udp://localhost:514)
-D, --debug Enable debugging output
-V, --trace Trace the raw protocol
Expand Down
9 changes: 9 additions & 0 deletions vendor/manifest
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@
"branch": "master",
"path": "/blowfish",
"notests": true
},
{
"importpath": "golang.org/x/sys/windows",
"repository": "https://go.googlesource.com/sys",
"vcs": "git",
"revision": "d75a52659825e75fff6158388dddc6a5b04f9ba5",
"branch": "master",
"path": "windows",
"notests": true
}
]
}

0 comments on commit 1df5af0

Please sign in to comment.