summaryrefslogtreecommitdiffstats
path: root/src/log
diff options
context:
space:
mode:
Diffstat (limited to 'src/log')
-rw-r--r--src/log/example_test.go41
-rw-r--r--src/log/log.go380
-rw-r--r--src/log/log_test.go214
-rw-r--r--src/log/syslog/doc.go24
-rw-r--r--src/log/syslog/example_test.go24
-rw-r--r--src/log/syslog/syslog.go319
-rw-r--r--src/log/syslog/syslog_test.go414
-rw-r--r--src/log/syslog/syslog_unix.go30
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, &timestamp, &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, &timestamp, &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")
+}