func emitLogRecord(lr *logRecord, sr *serveRecord, target *logplexc.Client, isAudit bool, exit exitFn) { // Buffer to format the complete log message in. msgFmtBuf := bytes.Buffer{} // Helps with formatting a series of nullable strings. catOptionalField := func(prefix string, maybePresent *string) { if maybePresent != nil { if prefix != "" { msgFmtBuf.WriteString(prefix) msgFmtBuf.WriteString(": ") } msgFmtBuf.WriteString(*maybePresent) msgFmtBuf.WriteByte('\n') } } if sr.Prefix != "" { msgFmtBuf.WriteString(sr.Prefix) msgFmtBuf.WriteString(" ") } if isAudit { // The audit endpoint may be multiplexed, so add the // identity to help tell log records apart. msgFmtBuf.WriteString("instance_type=shogun identity=" + sr.I + " ") } catOptionalField("", lr.ErrMessage) catOptionalField("Detail", lr.ErrDetail) catOptionalField("Hint", lr.ErrHint) catOptionalField("Query", lr.UserQuery) err := target.BufferMessage(134, time.Now(), "postgres", "postgres."+strconv.Itoa(int(lr.Pid)), msgFmtBuf.Bytes()) if err != nil { exit(err) } }
func logWorker(die dieCh, l net.Listener, cfg logplexc.Config, sr *serveRecord) { // Make world-writable so anything can connect and send logs. // This may be be worth locking down more, but as-is unless // pg_logplexcollector and the Postgres server share the same // running user common umasks will be useless. fi, err := os.Stat(sr.P) if err != nil { log.Fatalf( "exiting, cannot stat just created socket %q: %v", sr.P, err) } err = os.Chmod(sr.P, fi.Mode().Perm()|0222) if err != nil { log.Fatalf( "exiting, cannot make just created socket "+ "world-writable %q: %v", sr.P, err) } for { select { case <-die: log.Print("listener exits normally from die request") return default: break } conn, err := l.Accept() if err != nil { log.Printf("accept error: %v", err) } if err != nil { log.Fatalf("serve database suffers unrecoverable "+ "error: %v", err) } go func() { stream := core.NewBackendStream(conn) var exit exitFn exit = func(args ...interface{}) { if len(args) == 1 { log.Printf("Disconnect client: %v", args[0]) } else if len(args) > 1 { if s, ok := args[0].(string); ok { log.Printf(s, args[1:]...) } else { // Not an intended use case, but do // one's best to print something. log.Printf("Got a malformed exit: %v", args) } } panic(&exit) } // Recovers from panic and exits in an orderly manner if (and // only if) exit() is called; otherwise propagate the panic // normally. defer func() { conn.Close() // &exit is used as a sentinel value. if r := recover(); r != nil && r != &exit { panic(r) } }() var msgInit msgInit msgInit = func(m *core.Message, exit exitFn) { err = stream.Next(m) if err == io.EOF { exit("postgres client disconnects") } else if err != nil { exit("could not read next message: %v", err) } } // Protocol start-up; packets that are only received once. processVerMsg(msgInit, exit) ident := processIdentMsg(msgInit, exit) log.Printf("client connects with identifier %q", ident) // Resolve the identifier to a serve if sr.I != ident { exit("got unexpected identifier for socket: "+ "path %s, expected %s, got %s", sr.P, sr.I, ident) } // Set up client with serve client := func(cfg logplexc.Config, url *url.URL) *logplexc.Client { cfg.Logplex = *url client, err := logplexc.NewClient(&cfg) if err != nil { exit(err) } return client } primary := client(cfg, &sr.u) var audit *logplexc.Client if sr.audit != nil { audit = client(cfg, sr.audit) } defer func() { primary.Close() if audit != nil { audit.Close() } }() processLogMsg(die, primary, audit, msgInit, sr, exit) }() } }