Example #1
0
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)
	}
}
Example #2
0
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)
		}()
	}
}