diff options
Diffstat (limited to 'src/log')
-rw-r--r-- | src/log/example_test.go | 41 | ||||
-rw-r--r-- | src/log/log.go | 380 | ||||
-rw-r--r-- | src/log/log_test.go | 214 | ||||
-rw-r--r-- | src/log/syslog/doc.go | 24 | ||||
-rw-r--r-- | src/log/syslog/example_test.go | 24 | ||||
-rw-r--r-- | src/log/syslog/syslog.go | 319 | ||||
-rw-r--r-- | src/log/syslog/syslog_test.go | 414 | ||||
-rw-r--r-- | src/log/syslog/syslog_unix.go | 30 |
8 files changed, 1446 insertions, 0 deletions
diff --git a/src/log/example_test.go b/src/log/example_test.go new file mode 100644 index 0000000..769d076 --- /dev/null +++ b/src/log/example_test.go @@ -0,0 +1,41 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package log_test + +import ( + "bytes" + "fmt" + "log" +) + +func ExampleLogger() { + var ( + buf bytes.Buffer + logger = log.New(&buf, "logger: ", log.Lshortfile) + ) + + logger.Print("Hello, log file!") + + fmt.Print(&buf) + // Output: + // logger: example_test.go:19: Hello, log file! +} + +func ExampleLogger_Output() { + var ( + buf bytes.Buffer + logger = log.New(&buf, "INFO: ", log.Lshortfile) + + infof = func(info string) { + logger.Output(2, info) + } + ) + + infof("Hello world") + + fmt.Print(&buf) + // Output: + // INFO: example_test.go:36: Hello world +} diff --git a/src/log/log.go b/src/log/log.go new file mode 100644 index 0000000..b77af29 --- /dev/null +++ b/src/log/log.go @@ -0,0 +1,380 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package log implements a simple logging package. It defines a type, Logger, +// with methods for formatting output. It also has a predefined 'standard' +// Logger accessible through helper functions Print[f|ln], Fatal[f|ln], and +// Panic[f|ln], which are easier to use than creating a Logger manually. +// That logger writes to standard error and prints the date and time +// of each logged message. +// Every log message is output on a separate line: if the message being +// printed does not end in a newline, the logger will add one. +// The Fatal functions call os.Exit(1) after writing the log message. +// The Panic functions call panic after writing the log message. +package log + +import ( + "fmt" + "io" + "os" + "runtime" + "sync" + "time" +) + +// These flags define which text to prefix to each log entry generated by the Logger. +// Bits are or'ed together to control what's printed. +// With the exception of the Lmsgprefix flag, there is no +// control over the order they appear (the order listed here) +// or the format they present (as described in the comments). +// The prefix is followed by a colon only when Llongfile or Lshortfile +// is specified. +// For example, flags Ldate | Ltime (or LstdFlags) produce, +// 2009/01/23 01:23:23 message +// while flags Ldate | Ltime | Lmicroseconds | Llongfile produce, +// 2009/01/23 01:23:23.123123 /a/b/c/d.go:23: message +const ( + Ldate = 1 << iota // the date in the local time zone: 2009/01/23 + Ltime // the time in the local time zone: 01:23:23 + Lmicroseconds // microsecond resolution: 01:23:23.123123. assumes Ltime. + Llongfile // full file name and line number: /a/b/c/d.go:23 + Lshortfile // final file name element and line number: d.go:23. overrides Llongfile + LUTC // if Ldate or Ltime is set, use UTC rather than the local time zone + Lmsgprefix // move the "prefix" from the beginning of the line to before the message + LstdFlags = Ldate | Ltime // initial values for the standard logger +) + +// A Logger represents an active logging object that generates lines of +// output to an io.Writer. Each logging operation makes a single call to +// the Writer's Write method. A Logger can be used simultaneously from +// multiple goroutines; it guarantees to serialize access to the Writer. +type Logger struct { + mu sync.Mutex // ensures atomic writes; protects the following fields + prefix string // prefix on each line to identify the logger (but see Lmsgprefix) + flag int // properties + out io.Writer // destination for output + buf []byte // for accumulating text to write +} + +// New creates a new Logger. The out variable sets the +// destination to which log data will be written. +// The prefix appears at the beginning of each generated log line, or +// after the log header if the Lmsgprefix flag is provided. +// The flag argument defines the logging properties. +func New(out io.Writer, prefix string, flag int) *Logger { + return &Logger{out: out, prefix: prefix, flag: flag} +} + +// SetOutput sets the output destination for the logger. +func (l *Logger) SetOutput(w io.Writer) { + l.mu.Lock() + defer l.mu.Unlock() + l.out = w +} + +var std = New(os.Stderr, "", LstdFlags) + +// Default returns the standard logger used by the package-level output functions. +func Default() *Logger { return std } + +// Cheap integer to fixed-width decimal ASCII. Give a negative width to avoid zero-padding. +func itoa(buf *[]byte, i int, wid int) { + // Assemble decimal in reverse order. + var b [20]byte + bp := len(b) - 1 + for i >= 10 || wid > 1 { + wid-- + q := i / 10 + b[bp] = byte('0' + i - q*10) + bp-- + i = q + } + // i < 10 + b[bp] = byte('0' + i) + *buf = append(*buf, b[bp:]...) +} + +// formatHeader writes log header to buf in following order: +// * l.prefix (if it's not blank and Lmsgprefix is unset), +// * date and/or time (if corresponding flags are provided), +// * file and line number (if corresponding flags are provided), +// * l.prefix (if it's not blank and Lmsgprefix is set). +func (l *Logger) formatHeader(buf *[]byte, t time.Time, file string, line int) { + if l.flag&Lmsgprefix == 0 { + *buf = append(*buf, l.prefix...) + } + if l.flag&(Ldate|Ltime|Lmicroseconds) != 0 { + if l.flag&LUTC != 0 { + t = t.UTC() + } + if l.flag&Ldate != 0 { + year, month, day := t.Date() + itoa(buf, year, 4) + *buf = append(*buf, '/') + itoa(buf, int(month), 2) + *buf = append(*buf, '/') + itoa(buf, day, 2) + *buf = append(*buf, ' ') + } + if l.flag&(Ltime|Lmicroseconds) != 0 { + hour, min, sec := t.Clock() + itoa(buf, hour, 2) + *buf = append(*buf, ':') + itoa(buf, min, 2) + *buf = append(*buf, ':') + itoa(buf, sec, 2) + if l.flag&Lmicroseconds != 0 { + *buf = append(*buf, '.') + itoa(buf, t.Nanosecond()/1e3, 6) + } + *buf = append(*buf, ' ') + } + } + if l.flag&(Lshortfile|Llongfile) != 0 { + if l.flag&Lshortfile != 0 { + short := file + for i := len(file) - 1; i > 0; i-- { + if file[i] == '/' { + short = file[i+1:] + break + } + } + file = short + } + *buf = append(*buf, file...) + *buf = append(*buf, ':') + itoa(buf, line, -1) + *buf = append(*buf, ": "...) + } + if l.flag&Lmsgprefix != 0 { + *buf = append(*buf, l.prefix...) + } +} + +// Output writes the output for a logging event. The string s contains +// the text to print after the prefix specified by the flags of the +// Logger. A newline is appended if the last character of s is not +// already a newline. Calldepth is used to recover the PC and is +// provided for generality, although at the moment on all pre-defined +// paths it will be 2. +func (l *Logger) Output(calldepth int, s string) error { + now := time.Now() // get this early. + var file string + var line int + l.mu.Lock() + defer l.mu.Unlock() + if l.flag&(Lshortfile|Llongfile) != 0 { + // Release lock while getting caller info - it's expensive. + l.mu.Unlock() + var ok bool + _, file, line, ok = runtime.Caller(calldepth) + if !ok { + file = "???" + line = 0 + } + l.mu.Lock() + } + l.buf = l.buf[:0] + l.formatHeader(&l.buf, now, file, line) + l.buf = append(l.buf, s...) + if len(s) == 0 || s[len(s)-1] != '\n' { + l.buf = append(l.buf, '\n') + } + _, err := l.out.Write(l.buf) + return err +} + +// Printf calls l.Output to print to the logger. +// Arguments are handled in the manner of fmt.Printf. +func (l *Logger) Printf(format string, v ...interface{}) { + l.Output(2, fmt.Sprintf(format, v...)) +} + +// Print calls l.Output to print to the logger. +// Arguments are handled in the manner of fmt.Print. +func (l *Logger) Print(v ...interface{}) { l.Output(2, fmt.Sprint(v...)) } + +// Println calls l.Output to print to the logger. +// Arguments are handled in the manner of fmt.Println. +func (l *Logger) Println(v ...interface{}) { l.Output(2, fmt.Sprintln(v...)) } + +// Fatal is equivalent to l.Print() followed by a call to os.Exit(1). +func (l *Logger) Fatal(v ...interface{}) { + l.Output(2, fmt.Sprint(v...)) + os.Exit(1) +} + +// Fatalf is equivalent to l.Printf() followed by a call to os.Exit(1). +func (l *Logger) Fatalf(format string, v ...interface{}) { + l.Output(2, fmt.Sprintf(format, v...)) + os.Exit(1) +} + +// Fatalln is equivalent to l.Println() followed by a call to os.Exit(1). +func (l *Logger) Fatalln(v ...interface{}) { + l.Output(2, fmt.Sprintln(v...)) + os.Exit(1) +} + +// Panic is equivalent to l.Print() followed by a call to panic(). +func (l *Logger) Panic(v ...interface{}) { + s := fmt.Sprint(v...) + l.Output(2, s) + panic(s) +} + +// Panicf is equivalent to l.Printf() followed by a call to panic(). +func (l *Logger) Panicf(format string, v ...interface{}) { + s := fmt.Sprintf(format, v...) + l.Output(2, s) + panic(s) +} + +// Panicln is equivalent to l.Println() followed by a call to panic(). +func (l *Logger) Panicln(v ...interface{}) { + s := fmt.Sprintln(v...) + l.Output(2, s) + panic(s) +} + +// Flags returns the output flags for the logger. +// The flag bits are Ldate, Ltime, and so on. +func (l *Logger) Flags() int { + l.mu.Lock() + defer l.mu.Unlock() + return l.flag +} + +// SetFlags sets the output flags for the logger. +// The flag bits are Ldate, Ltime, and so on. +func (l *Logger) SetFlags(flag int) { + l.mu.Lock() + defer l.mu.Unlock() + l.flag = flag +} + +// Prefix returns the output prefix for the logger. +func (l *Logger) Prefix() string { + l.mu.Lock() + defer l.mu.Unlock() + return l.prefix +} + +// SetPrefix sets the output prefix for the logger. +func (l *Logger) SetPrefix(prefix string) { + l.mu.Lock() + defer l.mu.Unlock() + l.prefix = prefix +} + +// Writer returns the output destination for the logger. +func (l *Logger) Writer() io.Writer { + l.mu.Lock() + defer l.mu.Unlock() + return l.out +} + +// SetOutput sets the output destination for the standard logger. +func SetOutput(w io.Writer) { + std.mu.Lock() + defer std.mu.Unlock() + std.out = w +} + +// Flags returns the output flags for the standard logger. +// The flag bits are Ldate, Ltime, and so on. +func Flags() int { + return std.Flags() +} + +// SetFlags sets the output flags for the standard logger. +// The flag bits are Ldate, Ltime, and so on. +func SetFlags(flag int) { + std.SetFlags(flag) +} + +// Prefix returns the output prefix for the standard logger. +func Prefix() string { + return std.Prefix() +} + +// SetPrefix sets the output prefix for the standard logger. +func SetPrefix(prefix string) { + std.SetPrefix(prefix) +} + +// Writer returns the output destination for the standard logger. +func Writer() io.Writer { + return std.Writer() +} + +// These functions write to the standard logger. + +// Print calls Output to print to the standard logger. +// Arguments are handled in the manner of fmt.Print. +func Print(v ...interface{}) { + std.Output(2, fmt.Sprint(v...)) +} + +// Printf calls Output to print to the standard logger. +// Arguments are handled in the manner of fmt.Printf. +func Printf(format string, v ...interface{}) { + std.Output(2, fmt.Sprintf(format, v...)) +} + +// Println calls Output to print to the standard logger. +// Arguments are handled in the manner of fmt.Println. +func Println(v ...interface{}) { + std.Output(2, fmt.Sprintln(v...)) +} + +// Fatal is equivalent to Print() followed by a call to os.Exit(1). +func Fatal(v ...interface{}) { + std.Output(2, fmt.Sprint(v...)) + os.Exit(1) +} + +// Fatalf is equivalent to Printf() followed by a call to os.Exit(1). +func Fatalf(format string, v ...interface{}) { + std.Output(2, fmt.Sprintf(format, v...)) + os.Exit(1) +} + +// Fatalln is equivalent to Println() followed by a call to os.Exit(1). +func Fatalln(v ...interface{}) { + std.Output(2, fmt.Sprintln(v...)) + os.Exit(1) +} + +// Panic is equivalent to Print() followed by a call to panic(). +func Panic(v ...interface{}) { + s := fmt.Sprint(v...) + std.Output(2, s) + panic(s) +} + +// Panicf is equivalent to Printf() followed by a call to panic(). +func Panicf(format string, v ...interface{}) { + s := fmt.Sprintf(format, v...) + std.Output(2, s) + panic(s) +} + +// Panicln is equivalent to Println() followed by a call to panic(). +func Panicln(v ...interface{}) { + s := fmt.Sprintln(v...) + std.Output(2, s) + panic(s) +} + +// Output writes the output for a logging event. The string s contains +// the text to print after the prefix specified by the flags of the +// Logger. A newline is appended if the last character of s is not +// already a newline. Calldepth is the count of the number of +// frames to skip when computing the file name and line number +// if Llongfile or Lshortfile is set; a value of 1 will print the details +// for the caller of Output. +func Output(calldepth int, s string) error { + return std.Output(calldepth+1, s) // +1 for this frame. +} diff --git a/src/log/log_test.go b/src/log/log_test.go new file mode 100644 index 0000000..5be8e82 --- /dev/null +++ b/src/log/log_test.go @@ -0,0 +1,214 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package log + +// These tests are too simple. + +import ( + "bytes" + "fmt" + "os" + "regexp" + "strings" + "testing" + "time" +) + +const ( + Rdate = `[0-9][0-9][0-9][0-9]/[0-9][0-9]/[0-9][0-9]` + Rtime = `[0-9][0-9]:[0-9][0-9]:[0-9][0-9]` + Rmicroseconds = `\.[0-9][0-9][0-9][0-9][0-9][0-9]` + Rline = `(60|62):` // must update if the calls to l.Printf / l.Print below move + Rlongfile = `.*/[A-Za-z0-9_\-]+\.go:` + Rline + Rshortfile = `[A-Za-z0-9_\-]+\.go:` + Rline +) + +type tester struct { + flag int + prefix string + pattern string // regexp that log output must match; we add ^ and expected_text$ always +} + +var tests = []tester{ + // individual pieces: + {0, "", ""}, + {0, "XXX", "XXX"}, + {Ldate, "", Rdate + " "}, + {Ltime, "", Rtime + " "}, + {Ltime | Lmsgprefix, "XXX", Rtime + " XXX"}, + {Ltime | Lmicroseconds, "", Rtime + Rmicroseconds + " "}, + {Lmicroseconds, "", Rtime + Rmicroseconds + " "}, // microsec implies time + {Llongfile, "", Rlongfile + " "}, + {Lshortfile, "", Rshortfile + " "}, + {Llongfile | Lshortfile, "", Rshortfile + " "}, // shortfile overrides longfile + // everything at once: + {Ldate | Ltime | Lmicroseconds | Llongfile, "XXX", "XXX" + Rdate + " " + Rtime + Rmicroseconds + " " + Rlongfile + " "}, + {Ldate | Ltime | Lmicroseconds | Lshortfile, "XXX", "XXX" + Rdate + " " + Rtime + Rmicroseconds + " " + Rshortfile + " "}, + {Ldate | Ltime | Lmicroseconds | Llongfile | Lmsgprefix, "XXX", Rdate + " " + Rtime + Rmicroseconds + " " + Rlongfile + " XXX"}, + {Ldate | Ltime | Lmicroseconds | Lshortfile | Lmsgprefix, "XXX", Rdate + " " + Rtime + Rmicroseconds + " " + Rshortfile + " XXX"}, +} + +// Test using Println("hello", 23, "world") or using Printf("hello %d world", 23) +func testPrint(t *testing.T, flag int, prefix string, pattern string, useFormat bool) { + buf := new(bytes.Buffer) + SetOutput(buf) + SetFlags(flag) + SetPrefix(prefix) + if useFormat { + Printf("hello %d world", 23) + } else { + Println("hello", 23, "world") + } + line := buf.String() + line = line[0 : len(line)-1] + pattern = "^" + pattern + "hello 23 world$" + matched, err := regexp.MatchString(pattern, line) + if err != nil { + t.Fatal("pattern did not compile:", err) + } + if !matched { + t.Errorf("log output should match %q is %q", pattern, line) + } + SetOutput(os.Stderr) +} + +func TestDefault(t *testing.T) { + if got := Default(); got != std { + t.Errorf("Default [%p] should be std [%p]", got, std) + } +} + +func TestAll(t *testing.T) { + for _, testcase := range tests { + testPrint(t, testcase.flag, testcase.prefix, testcase.pattern, false) + testPrint(t, testcase.flag, testcase.prefix, testcase.pattern, true) + } +} + +func TestOutput(t *testing.T) { + const testString = "test" + var b bytes.Buffer + l := New(&b, "", 0) + l.Println(testString) + if expect := testString + "\n"; b.String() != expect { + t.Errorf("log output should match %q is %q", expect, b.String()) + } +} + +func TestOutputRace(t *testing.T) { + var b bytes.Buffer + l := New(&b, "", 0) + for i := 0; i < 100; i++ { + go func() { + l.SetFlags(0) + }() + l.Output(0, "") + } +} + +func TestFlagAndPrefixSetting(t *testing.T) { + var b bytes.Buffer + l := New(&b, "Test:", LstdFlags) + f := l.Flags() + if f != LstdFlags { + t.Errorf("Flags 1: expected %x got %x", LstdFlags, f) + } + l.SetFlags(f | Lmicroseconds) + f = l.Flags() + if f != LstdFlags|Lmicroseconds { + t.Errorf("Flags 2: expected %x got %x", LstdFlags|Lmicroseconds, f) + } + p := l.Prefix() + if p != "Test:" { + t.Errorf(`Prefix: expected "Test:" got %q`, p) + } + l.SetPrefix("Reality:") + p = l.Prefix() + if p != "Reality:" { + t.Errorf(`Prefix: expected "Reality:" got %q`, p) + } + // Verify a log message looks right, with our prefix and microseconds present. + l.Print("hello") + pattern := "^Reality:" + Rdate + " " + Rtime + Rmicroseconds + " hello\n" + matched, err := regexp.Match(pattern, b.Bytes()) + if err != nil { + t.Fatalf("pattern %q did not compile: %s", pattern, err) + } + if !matched { + t.Error("message did not match pattern") + } +} + +func TestUTCFlag(t *testing.T) { + var b bytes.Buffer + l := New(&b, "Test:", LstdFlags) + l.SetFlags(Ldate | Ltime | LUTC) + // Verify a log message looks right in the right time zone. Quantize to the second only. + now := time.Now().UTC() + l.Print("hello") + want := fmt.Sprintf("Test:%d/%.2d/%.2d %.2d:%.2d:%.2d hello\n", + now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second()) + got := b.String() + if got == want { + return + } + // It's possible we crossed a second boundary between getting now and logging, + // so add a second and try again. This should very nearly always work. + now = now.Add(time.Second) + want = fmt.Sprintf("Test:%d/%.2d/%.2d %.2d:%.2d:%.2d hello\n", + now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second()) + if got == want { + return + } + t.Errorf("got %q; want %q", got, want) +} + +func TestEmptyPrintCreatesLine(t *testing.T) { + var b bytes.Buffer + l := New(&b, "Header:", LstdFlags) + l.Print() + l.Println("non-empty") + output := b.String() + if n := strings.Count(output, "Header"); n != 2 { + t.Errorf("expected 2 headers, got %d", n) + } + if n := strings.Count(output, "\n"); n != 2 { + t.Errorf("expected 2 lines, got %d", n) + } +} + +func BenchmarkItoa(b *testing.B) { + dst := make([]byte, 0, 64) + for i := 0; i < b.N; i++ { + dst = dst[0:0] + itoa(&dst, 2015, 4) // year + itoa(&dst, 1, 2) // month + itoa(&dst, 30, 2) // day + itoa(&dst, 12, 2) // hour + itoa(&dst, 56, 2) // minute + itoa(&dst, 0, 2) // second + itoa(&dst, 987654, 6) // microsecond + } +} + +func BenchmarkPrintln(b *testing.B) { + const testString = "test" + var buf bytes.Buffer + l := New(&buf, "", LstdFlags) + for i := 0; i < b.N; i++ { + buf.Reset() + l.Println(testString) + } +} + +func BenchmarkPrintlnNoFlags(b *testing.B) { + const testString = "test" + var buf bytes.Buffer + l := New(&buf, "", 0) + for i := 0; i < b.N; i++ { + buf.Reset() + l.Println(testString) + } +} diff --git a/src/log/syslog/doc.go b/src/log/syslog/doc.go new file mode 100644 index 0000000..bd12bea --- /dev/null +++ b/src/log/syslog/doc.go @@ -0,0 +1,24 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package syslog provides a simple interface to the system log +// service. It can send messages to the syslog daemon using UNIX +// domain sockets, UDP or TCP. +// +// Only one call to Dial is necessary. On write failures, +// the syslog client will attempt to reconnect to the server +// and write again. +// +// The syslog package is frozen and is not accepting new features. +// Some external packages provide more functionality. See: +// +// https://godoc.org/?q=syslog +package syslog + +// BUG(brainman): This package is not implemented on Windows. As the +// syslog package is frozen, Windows users are encouraged to +// use a package outside of the standard library. For background, +// see https://golang.org/issue/1108. + +// BUG(akumar): This package is not implemented on Plan 9. diff --git a/src/log/syslog/example_test.go b/src/log/syslog/example_test.go new file mode 100644 index 0000000..9939765 --- /dev/null +++ b/src/log/syslog/example_test.go @@ -0,0 +1,24 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !windows && !plan9 +// +build !windows,!plan9 + +package syslog_test + +import ( + "fmt" + "log" + "log/syslog" +) + +func ExampleDial() { + sysLog, err := syslog.Dial("tcp", "localhost:1234", + syslog.LOG_WARNING|syslog.LOG_DAEMON, "demotag") + if err != nil { + log.Fatal(err) + } + fmt.Fprintf(sysLog, "This is a daemon warning with demotag.") + sysLog.Emerg("And this is a daemon emergency with demotag.") +} diff --git a/src/log/syslog/syslog.go b/src/log/syslog/syslog.go new file mode 100644 index 0000000..6abd4ad --- /dev/null +++ b/src/log/syslog/syslog.go @@ -0,0 +1,319 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !windows && !plan9 +// +build !windows,!plan9 + +package syslog + +import ( + "errors" + "fmt" + "log" + "net" + "os" + "strings" + "sync" + "time" +) + +// The Priority is a combination of the syslog facility and +// severity. For example, LOG_ALERT | LOG_FTP sends an alert severity +// message from the FTP facility. The default severity is LOG_EMERG; +// the default facility is LOG_KERN. +type Priority int + +const severityMask = 0x07 +const facilityMask = 0xf8 + +const ( + // Severity. + + // From /usr/include/sys/syslog.h. + // These are the same on Linux, BSD, and OS X. + LOG_EMERG Priority = iota + LOG_ALERT + LOG_CRIT + LOG_ERR + LOG_WARNING + LOG_NOTICE + LOG_INFO + LOG_DEBUG +) + +const ( + // Facility. + + // From /usr/include/sys/syslog.h. + // These are the same up to LOG_FTP on Linux, BSD, and OS X. + LOG_KERN Priority = iota << 3 + LOG_USER + LOG_MAIL + LOG_DAEMON + LOG_AUTH + LOG_SYSLOG + LOG_LPR + LOG_NEWS + LOG_UUCP + LOG_CRON + LOG_AUTHPRIV + LOG_FTP + _ // unused + _ // unused + _ // unused + _ // unused + LOG_LOCAL0 + LOG_LOCAL1 + LOG_LOCAL2 + LOG_LOCAL3 + LOG_LOCAL4 + LOG_LOCAL5 + LOG_LOCAL6 + LOG_LOCAL7 +) + +// A Writer is a connection to a syslog server. +type Writer struct { + priority Priority + tag string + hostname string + network string + raddr string + + mu sync.Mutex // guards conn + conn serverConn +} + +// This interface and the separate syslog_unix.go file exist for +// Solaris support as implemented by gccgo. On Solaris you cannot +// simply open a TCP connection to the syslog daemon. The gccgo +// sources have a syslog_solaris.go file that implements unixSyslog to +// return a type that satisfies this interface and simply calls the C +// library syslog function. +type serverConn interface { + writeString(p Priority, hostname, tag, s, nl string) error + close() error +} + +type netConn struct { + local bool + conn net.Conn +} + +// New establishes a new connection to the system log daemon. Each +// write to the returned writer sends a log message with the given +// priority (a combination of the syslog facility and severity) and +// prefix tag. If tag is empty, the os.Args[0] is used. +func New(priority Priority, tag string) (*Writer, error) { + return Dial("", "", priority, tag) +} + +// Dial establishes a connection to a log daemon by connecting to +// address raddr on the specified network. Each write to the returned +// writer sends a log message with the facility and severity +// (from priority) and tag. If tag is empty, the os.Args[0] is used. +// If network is empty, Dial will connect to the local syslog server. +// Otherwise, see the documentation for net.Dial for valid values +// of network and raddr. +func Dial(network, raddr string, priority Priority, tag string) (*Writer, error) { + if priority < 0 || priority > LOG_LOCAL7|LOG_DEBUG { + return nil, errors.New("log/syslog: invalid priority") + } + + if tag == "" { + tag = os.Args[0] + } + hostname, _ := os.Hostname() + + w := &Writer{ + priority: priority, + tag: tag, + hostname: hostname, + network: network, + raddr: raddr, + } + + w.mu.Lock() + defer w.mu.Unlock() + + err := w.connect() + if err != nil { + return nil, err + } + return w, err +} + +// connect makes a connection to the syslog server. +// It must be called with w.mu held. +func (w *Writer) connect() (err error) { + if w.conn != nil { + // ignore err from close, it makes sense to continue anyway + w.conn.close() + w.conn = nil + } + + if w.network == "" { + w.conn, err = unixSyslog() + if w.hostname == "" { + w.hostname = "localhost" + } + } else { + var c net.Conn + c, err = net.Dial(w.network, w.raddr) + if err == nil { + w.conn = &netConn{ + conn: c, + local: w.network == "unixgram" || w.network == "unix", + } + if w.hostname == "" { + w.hostname = c.LocalAddr().String() + } + } + } + return +} + +// Write sends a log message to the syslog daemon. +func (w *Writer) Write(b []byte) (int, error) { + return w.writeAndRetry(w.priority, string(b)) +} + +// Close closes a connection to the syslog daemon. +func (w *Writer) Close() error { + w.mu.Lock() + defer w.mu.Unlock() + + if w.conn != nil { + err := w.conn.close() + w.conn = nil + return err + } + return nil +} + +// Emerg logs a message with severity LOG_EMERG, ignoring the severity +// passed to New. +func (w *Writer) Emerg(m string) error { + _, err := w.writeAndRetry(LOG_EMERG, m) + return err +} + +// Alert logs a message with severity LOG_ALERT, ignoring the severity +// passed to New. +func (w *Writer) Alert(m string) error { + _, err := w.writeAndRetry(LOG_ALERT, m) + return err +} + +// Crit logs a message with severity LOG_CRIT, ignoring the severity +// passed to New. +func (w *Writer) Crit(m string) error { + _, err := w.writeAndRetry(LOG_CRIT, m) + return err +} + +// Err logs a message with severity LOG_ERR, ignoring the severity +// passed to New. +func (w *Writer) Err(m string) error { + _, err := w.writeAndRetry(LOG_ERR, m) + return err +} + +// Warning logs a message with severity LOG_WARNING, ignoring the +// severity passed to New. +func (w *Writer) Warning(m string) error { + _, err := w.writeAndRetry(LOG_WARNING, m) + return err +} + +// Notice logs a message with severity LOG_NOTICE, ignoring the +// severity passed to New. +func (w *Writer) Notice(m string) error { + _, err := w.writeAndRetry(LOG_NOTICE, m) + return err +} + +// Info logs a message with severity LOG_INFO, ignoring the severity +// passed to New. +func (w *Writer) Info(m string) error { + _, err := w.writeAndRetry(LOG_INFO, m) + return err +} + +// Debug logs a message with severity LOG_DEBUG, ignoring the severity +// passed to New. +func (w *Writer) Debug(m string) error { + _, err := w.writeAndRetry(LOG_DEBUG, m) + return err +} + +func (w *Writer) writeAndRetry(p Priority, s string) (int, error) { + pr := (w.priority & facilityMask) | (p & severityMask) + + w.mu.Lock() + defer w.mu.Unlock() + + if w.conn != nil { + if n, err := w.write(pr, s); err == nil { + return n, err + } + } + if err := w.connect(); err != nil { + return 0, err + } + return w.write(pr, s) +} + +// write generates and writes a syslog formatted string. The +// format is as follows: <PRI>TIMESTAMP HOSTNAME TAG[PID]: MSG +func (w *Writer) write(p Priority, msg string) (int, error) { + // ensure it ends in a \n + nl := "" + if !strings.HasSuffix(msg, "\n") { + nl = "\n" + } + + err := w.conn.writeString(p, w.hostname, w.tag, msg, nl) + if err != nil { + return 0, err + } + // Note: return the length of the input, not the number of + // bytes printed by Fprintf, because this must behave like + // an io.Writer. + return len(msg), nil +} + +func (n *netConn) writeString(p Priority, hostname, tag, msg, nl string) error { + if n.local { + // Compared to the network form below, the changes are: + // 1. Use time.Stamp instead of time.RFC3339. + // 2. Drop the hostname field from the Fprintf. + timestamp := time.Now().Format(time.Stamp) + _, err := fmt.Fprintf(n.conn, "<%d>%s %s[%d]: %s%s", + p, timestamp, + tag, os.Getpid(), msg, nl) + return err + } + timestamp := time.Now().Format(time.RFC3339) + _, err := fmt.Fprintf(n.conn, "<%d>%s %s %s[%d]: %s%s", + p, timestamp, hostname, + tag, os.Getpid(), msg, nl) + return err +} + +func (n *netConn) close() error { + return n.conn.Close() +} + +// NewLogger creates a log.Logger whose output is written to the +// system log service with the specified priority, a combination of +// the syslog facility and severity. The logFlag argument is the flag +// set passed through to log.New to create the Logger. +func NewLogger(p Priority, logFlag int) (*log.Logger, error) { + s, err := New(p, "") + if err != nil { + return nil, err + } + return log.New(s, "", logFlag), nil +} diff --git a/src/log/syslog/syslog_test.go b/src/log/syslog/syslog_test.go new file mode 100644 index 0000000..62c6250 --- /dev/null +++ b/src/log/syslog/syslog_test.go @@ -0,0 +1,414 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !windows && !plan9 && !js +// +build !windows,!plan9,!js + +package syslog + +import ( + "bufio" + "fmt" + "io" + "log" + "net" + "os" + "runtime" + "sync" + "testing" + "time" +) + +func runPktSyslog(c net.PacketConn, done chan<- string) { + var buf [4096]byte + var rcvd string + ct := 0 + for { + var n int + var err error + + c.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) + n, _, err = c.ReadFrom(buf[:]) + rcvd += string(buf[:n]) + if err != nil { + if oe, ok := err.(*net.OpError); ok { + if ct < 3 && oe.Temporary() { + ct++ + continue + } + } + break + } + } + c.Close() + done <- rcvd +} + +var crashy = false + +func testableNetwork(network string) bool { + switch network { + case "unix", "unixgram": + switch runtime.GOOS { + case "ios", "android": + return false + } + } + return true +} + +func runStreamSyslog(l net.Listener, done chan<- string, wg *sync.WaitGroup) { + for { + var c net.Conn + var err error + if c, err = l.Accept(); err != nil { + return + } + wg.Add(1) + go func(c net.Conn) { + defer wg.Done() + c.SetReadDeadline(time.Now().Add(5 * time.Second)) + b := bufio.NewReader(c) + for ct := 1; !crashy || ct&7 != 0; ct++ { + s, err := b.ReadString('\n') + if err != nil { + break + } + done <- s + } + c.Close() + }(c) + } +} + +func startServer(n, la string, done chan<- string) (addr string, sock io.Closer, wg *sync.WaitGroup) { + if n == "udp" || n == "tcp" { + la = "127.0.0.1:0" + } else { + // unix and unixgram: choose an address if none given + if la == "" { + // use os.CreateTemp to get a name that is unique + f, err := os.CreateTemp("", "syslogtest") + if err != nil { + log.Fatal("TempFile: ", err) + } + f.Close() + la = f.Name() + } + os.Remove(la) + } + + wg = new(sync.WaitGroup) + if n == "udp" || n == "unixgram" { + l, e := net.ListenPacket(n, la) + if e != nil { + log.Fatalf("startServer failed: %v", e) + } + addr = l.LocalAddr().String() + sock = l + wg.Add(1) + go func() { + defer wg.Done() + runPktSyslog(l, done) + }() + } else { + l, e := net.Listen(n, la) + if e != nil { + log.Fatalf("startServer failed: %v", e) + } + addr = l.Addr().String() + sock = l + wg.Add(1) + go func() { + defer wg.Done() + runStreamSyslog(l, done, wg) + }() + } + return +} + +func TestWithSimulated(t *testing.T) { + t.Parallel() + msg := "Test 123" + var transport []string + for _, n := range []string{"unix", "unixgram", "udp", "tcp"} { + if testableNetwork(n) { + transport = append(transport, n) + } + } + + for _, tr := range transport { + done := make(chan string) + addr, sock, srvWG := startServer(tr, "", done) + defer srvWG.Wait() + defer sock.Close() + if tr == "unix" || tr == "unixgram" { + defer os.Remove(addr) + } + s, err := Dial(tr, addr, LOG_INFO|LOG_USER, "syslog_test") + if err != nil { + t.Fatalf("Dial() failed: %v", err) + } + err = s.Info(msg) + if err != nil { + t.Fatalf("log failed: %v", err) + } + check(t, msg, <-done, tr) + s.Close() + } +} + +func TestFlap(t *testing.T) { + net := "unix" + if !testableNetwork(net) { + t.Skipf("skipping on %s/%s; 'unix' is not supported", runtime.GOOS, runtime.GOARCH) + } + + done := make(chan string) + addr, sock, srvWG := startServer(net, "", done) + defer srvWG.Wait() + defer os.Remove(addr) + defer sock.Close() + + s, err := Dial(net, addr, LOG_INFO|LOG_USER, "syslog_test") + if err != nil { + t.Fatalf("Dial() failed: %v", err) + } + msg := "Moo 2" + err = s.Info(msg) + if err != nil { + t.Fatalf("log failed: %v", err) + } + check(t, msg, <-done, net) + + // restart the server + _, sock2, srvWG2 := startServer(net, addr, done) + defer srvWG2.Wait() + defer sock2.Close() + + // and try retransmitting + msg = "Moo 3" + err = s.Info(msg) + if err != nil { + t.Fatalf("log failed: %v", err) + } + check(t, msg, <-done, net) + + s.Close() +} + +func TestNew(t *testing.T) { + if LOG_LOCAL7 != 23<<3 { + t.Fatalf("LOG_LOCAL7 has wrong value") + } + if testing.Short() { + // Depends on syslog daemon running, and sometimes it's not. + t.Skip("skipping syslog test during -short") + } + + s, err := New(LOG_INFO|LOG_USER, "the_tag") + if err != nil { + if err.Error() == "Unix syslog delivery error" { + t.Skip("skipping: syslogd not running") + } + t.Fatalf("New() failed: %s", err) + } + // Don't send any messages. + s.Close() +} + +func TestNewLogger(t *testing.T) { + if testing.Short() { + t.Skip("skipping syslog test during -short") + } + f, err := NewLogger(LOG_USER|LOG_INFO, 0) + if f == nil { + if err.Error() == "Unix syslog delivery error" { + t.Skip("skipping: syslogd not running") + } + t.Error(err) + } +} + +func TestDial(t *testing.T) { + if testing.Short() { + t.Skip("skipping syslog test during -short") + } + f, err := Dial("", "", (LOG_LOCAL7|LOG_DEBUG)+1, "syslog_test") + if f != nil { + t.Fatalf("Should have trapped bad priority") + } + f, err = Dial("", "", -1, "syslog_test") + if f != nil { + t.Fatalf("Should have trapped bad priority") + } + l, err := Dial("", "", LOG_USER|LOG_ERR, "syslog_test") + if err != nil { + if err.Error() == "Unix syslog delivery error" { + t.Skip("skipping: syslogd not running") + } + t.Fatalf("Dial() failed: %s", err) + } + l.Close() +} + +func check(t *testing.T, in, out, transport string) { + hostname, err := os.Hostname() + if err != nil { + t.Error("Error retrieving hostname") + return + } + + if transport == "unixgram" || transport == "unix" { + var month, date, ts string + var pid int + tmpl := fmt.Sprintf("<%d>%%s %%s %%s syslog_test[%%d]: %s\n", LOG_USER+LOG_INFO, in) + n, err := fmt.Sscanf(out, tmpl, &month, &date, &ts, &pid) + if n != 4 || err != nil { + t.Errorf("Got %q, does not match template %q (%d %s)", out, tmpl, n, err) + } + return + } + + // Non-UNIX domain transports. + var parsedHostname, timestamp string + var pid int + tmpl := fmt.Sprintf("<%d>%%s %%s syslog_test[%%d]: %s\n", LOG_USER+LOG_INFO, in) + n, err := fmt.Sscanf(out, tmpl, ×tamp, &parsedHostname, &pid) + if n != 3 || err != nil || hostname != parsedHostname { + t.Errorf("Got %q, does not match template %q (%d %s)", out, tmpl, n, err) + } +} + +func TestWrite(t *testing.T) { + t.Parallel() + tests := []struct { + pri Priority + pre string + msg string + exp string + }{ + {LOG_USER | LOG_ERR, "syslog_test", "", "%s %s syslog_test[%d]: \n"}, + {LOG_USER | LOG_ERR, "syslog_test", "write test", "%s %s syslog_test[%d]: write test\n"}, + // Write should not add \n if there already is one + {LOG_USER | LOG_ERR, "syslog_test", "write test 2\n", "%s %s syslog_test[%d]: write test 2\n"}, + } + + if hostname, err := os.Hostname(); err != nil { + t.Fatalf("Error retrieving hostname") + } else { + for _, test := range tests { + done := make(chan string) + addr, sock, srvWG := startServer("udp", "", done) + defer srvWG.Wait() + defer sock.Close() + l, err := Dial("udp", addr, test.pri, test.pre) + if err != nil { + t.Fatalf("syslog.Dial() failed: %v", err) + } + defer l.Close() + _, err = io.WriteString(l, test.msg) + if err != nil { + t.Fatalf("WriteString() failed: %v", err) + } + rcvd := <-done + test.exp = fmt.Sprintf("<%d>", test.pri) + test.exp + var parsedHostname, timestamp string + var pid int + if n, err := fmt.Sscanf(rcvd, test.exp, ×tamp, &parsedHostname, &pid); n != 3 || err != nil || hostname != parsedHostname { + t.Errorf("s.Info() = '%q', didn't match '%q' (%d %s)", rcvd, test.exp, n, err) + } + } + } +} + +func TestConcurrentWrite(t *testing.T) { + addr, sock, srvWG := startServer("udp", "", make(chan string, 1)) + defer srvWG.Wait() + defer sock.Close() + w, err := Dial("udp", addr, LOG_USER|LOG_ERR, "how's it going?") + if err != nil { + t.Fatalf("syslog.Dial() failed: %v", err) + } + var wg sync.WaitGroup + for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + defer wg.Done() + err := w.Info("test") + if err != nil { + t.Errorf("Info() failed: %v", err) + return + } + }() + } + wg.Wait() +} + +func TestConcurrentReconnect(t *testing.T) { + crashy = true + defer func() { crashy = false }() + + const N = 10 + const M = 100 + net := "unix" + if !testableNetwork(net) { + net = "tcp" + if !testableNetwork(net) { + t.Skipf("skipping on %s/%s; neither 'unix' or 'tcp' is supported", runtime.GOOS, runtime.GOARCH) + } + } + done := make(chan string, N*M) + addr, sock, srvWG := startServer(net, "", done) + if net == "unix" { + defer os.Remove(addr) + } + + // count all the messages arriving + count := make(chan int, 1) + go func() { + ct := 0 + for range done { + ct++ + // we are looking for 500 out of 1000 events + // here because lots of log messages are lost + // in buffers (kernel and/or bufio) + if ct > N*M/2 { + break + } + } + count <- ct + }() + + var wg sync.WaitGroup + wg.Add(N) + for i := 0; i < N; i++ { + go func() { + defer wg.Done() + w, err := Dial(net, addr, LOG_USER|LOG_ERR, "tag") + if err != nil { + t.Errorf("syslog.Dial() failed: %v", err) + return + } + defer w.Close() + for i := 0; i < M; i++ { + err := w.Info("test") + if err != nil { + t.Errorf("Info() failed: %v", err) + return + } + } + }() + } + wg.Wait() + sock.Close() + srvWG.Wait() + close(done) + + select { + case <-count: + case <-time.After(100 * time.Millisecond): + t.Error("timeout in concurrent reconnect") + } +} diff --git a/src/log/syslog/syslog_unix.go b/src/log/syslog/syslog_unix.go new file mode 100644 index 0000000..2e45f07 --- /dev/null +++ b/src/log/syslog/syslog_unix.go @@ -0,0 +1,30 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !windows && !plan9 +// +build !windows,!plan9 + +package syslog + +import ( + "errors" + "net" +) + +// unixSyslog opens a connection to the syslog daemon running on the +// local machine using a Unix domain socket. + +func unixSyslog() (conn serverConn, err error) { + logTypes := []string{"unixgram", "unix"} + logPaths := []string{"/dev/log", "/var/run/syslog", "/var/run/log"} + for _, network := range logTypes { + for _, path := range logPaths { + conn, err := net.Dial(network, path) + if err == nil { + return &netConn{conn: conn, local: true}, nil + } + } + } + return nil, errors.New("Unix syslog delivery error") +} |