mirror of
https://github.com/linuxkit/linuxkit.git
synced 2025-07-23 19:05:37 +00:00
memlogd: use kmsg format for reading the logs
Switch to a more formally-specified `kmsg`-style format for reading the logs. - update the spec in docs/logging.md - check for bad names in pkg/memlogd with unit test Signed-off-by: David Scott <dave.scott@docker.com>
This commit is contained in:
parent
fe64f33430
commit
a07ecf60d5
@ -42,32 +42,40 @@ network service, a service should be added to the yaml which connects to
|
|||||||
`memlogd` and streams the logs. The example program `logread` in the `memlogd`
|
`memlogd` and streams the logs. The example program `logread` in the `memlogd`
|
||||||
package demonstrates how to do this.
|
package demonstrates how to do this.
|
||||||
|
|
||||||
Usage examples:
|
### Message format
|
||||||
|
|
||||||
|
The format used to read logs is similar to [kmsg](https://www.kernel.org/doc/Documentation/ABI/testing/dev-kmsg):
|
||||||
|
```
|
||||||
|
<timestamp>,<log>;<body>
|
||||||
|
```
|
||||||
|
where `<timestamp>` is an RFC3339-formatted timestamp, `<log>` is the name of
|
||||||
|
the log (e.g. `docker-ce.out`) and `<body>` is the output. The `<log>` must
|
||||||
|
not contain the character `;`.
|
||||||
|
|
||||||
|
### Usage examples
|
||||||
```
|
```
|
||||||
/ # logread -f
|
/ # logread -f
|
||||||
2017-04-15T15:37:37Z memlogd memlogd started
|
2018-07-05T13:22:32Z,memlogd;memlogd started
|
||||||
2017-04-15T15:37:37Z 002-dhcpcd.stdout eth0: waiting for carrier
|
2018-07-05T13:22:32Z,onboot.001-dhcpcd.out;eth0: waiting for carrier
|
||||||
2017-04-15T15:37:37Z 002-dhcpcd.stdout eth0: carrier acquired
|
2018-07-05T13:22:32Z,onboot.001-dhcpcd.err;eth0: could not detect a useable init
|
||||||
2017-04-15T15:37:37Z 002-dhcpcd.stdout DUID 00:01:00:01:20:84:fa:c1:02:50:00:00:00:24
|
system
|
||||||
2017-04-15T15:37:37Z 002-dhcpcd.stdout eth0: IAID 00:00:00:24
|
2018-07-05T13:22:32Z,onboot.001-dhcpcd.out;eth0: carrier acquired
|
||||||
2017-04-15T15:37:37Z 002-dhcpcd.stdout eth0: adding address fe80::84e3:ca52:2590:fe80
|
2018-07-05T13:22:32Z,onboot.001-dhcpcd.out;DUID 00:01:00:01:22:d0:d8:18:02:50:00:00:00:02
|
||||||
2017-04-15T15:37:37Z 002-dhcpcd.stdout eth0: soliciting an IPv6 router
|
2018-07-05T13:22:32Z,onboot.001-dhcpcd.out;eth0: IAID 00:00:00:02
|
||||||
2017-04-15T15:37:37Z 002-dhcpcd.stdout eth0: soliciting a DHCP lease
|
2018-07-05T13:22:32Z,onboot.001-dhcpcd.out;eth0: adding address fe80::d33a:3936:
|
||||||
2017-04-15T15:37:37Z 002-dhcpcd.stdout eth0: offered 192.168.65.37 from 192.168.65.1 `vpnkit'
|
2ee4:5c8c
|
||||||
2017-04-15T15:37:37Z 002-dhcpcd.stdout eth0: leased 192.168.65.37 for 7199 seconds
|
2018-07-05T13:22:32Z,onboot.001-dhcpcd.out;eth0: soliciting an IPv6 router
|
||||||
2017-04-15T15:37:37Z 002-dhcpcd.stdout eth0: adding route to 192.168.65.0/24
|
2018-07-05T13:22:32Z,onboot.001-dhcpcd.out;eth0: soliciting a DHCP lease
|
||||||
2017-04-15T15:37:37Z 002-dhcpcd.stdout eth0: adding default route via 192.168.65.1
|
2018-07-05T13:22:32Z,onboot.001-dhcpcd.out;eth0: offered 192.168.65.4 from 192.1
|
||||||
2017-04-15T15:37:37Z 002-dhcpcd.stdout exiting due to oneshot
|
68.65.1 `vpnkit'
|
||||||
2017-04-15T15:37:37Z 002-dhcpcd.stdout dhcpcd exited
|
2018-07-05T13:22:32Z,onboot.001-dhcpcd.out;eth0: leased 192.168.65.4 for 7200 se
|
||||||
2017-04-15T15:37:37Z rngd.stderr Unable to open file: /dev/tpm0
|
conds
|
||||||
|
2018-07-05T13:22:32Z,onboot.001-dhcpcd.out;eth0: adding route to 192.168.65.0/24
|
||||||
|
2018-07-05T13:22:32Z,onboot.001-dhcpcd.out;eth0: adding default route via 192.16
|
||||||
|
8.65.1
|
||||||
|
2018-07-05T13:22:32Z,onboot.001-dhcpcd.out;exiting due to oneshot
|
||||||
|
2018-07-05T13:22:32Z,onboot.001-dhcpcd.out;dhcpcd exited
|
||||||
^C
|
^C
|
||||||
/ # logwrite echo testing123
|
|
||||||
testing123
|
|
||||||
/ # logread | tail -n1
|
|
||||||
2017-04-15T15:37:45Z echo.stdout testing123
|
|
||||||
/ # echo -en "GET / HTTP/1.0\n\n" | nc localhost 80 > /dev/null
|
|
||||||
/ # logread | grep nginx
|
|
||||||
2017-04-15T15:42:40Z nginx.stdout 127.0.0.1 - - [15/Apr/2017:15:42:40 +0000] "GET / HTTP/1.0" 200 612 "-" "-" "-"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Current issues and limitations:
|
Current issues and limitations:
|
||||||
|
@ -66,7 +66,7 @@ func logQueryHandler(l *connListener) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (msg *logEntry) String() string {
|
func (msg *logEntry) String() string {
|
||||||
return fmt.Sprintf("%s %s %s", msg.time.Format(time.RFC3339), msg.source, msg.msg)
|
return fmt.Sprintf("%s,%s;%s", msg.time.Format(time.RFC3339), msg.source, msg.msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ringBufferHandler(ringSize, chanSize int, logCh chan logEntry, queryMsgChan chan queryMessage) {
|
func ringBufferHandler(ringSize, chanSize int, logCh chan logEntry, queryMsgChan chan queryMessage) {
|
||||||
@ -220,6 +220,23 @@ func readLogFromFd(maxLineLen int, fd int, source string, logCh chan logEntry) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loggingRequestHandler(lineMaxLength int, logCh chan logEntry, fdMsgChan chan fdMessage) {
|
||||||
|
for true {
|
||||||
|
select {
|
||||||
|
case msg := <-fdMsgChan: // incoming fd
|
||||||
|
if strings.Contains(msg.name, ";") {
|
||||||
|
// The log message spec bans ";" in the log names
|
||||||
|
doLog(logCh, fmt.Sprintf("ERROR: cannot register log with name '%s' as it contains ;", msg.name))
|
||||||
|
if err := syscall.Close(msg.fd); err != nil {
|
||||||
|
doLog(logCh, fmt.Sprintf("ERROR: failed to close fd: %s", err))
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go readLogFromFd(lineMaxLength, msg.fd, msg.name, logCh)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
@ -304,16 +321,14 @@ func main() {
|
|||||||
fdMsgChan := make(chan fdMessage)
|
fdMsgChan := make(chan fdMessage)
|
||||||
queryMsgChan := make(chan queryMessage)
|
queryMsgChan := make(chan queryMessage)
|
||||||
|
|
||||||
|
// receive fds from the logging Unix domain socket and send on fdMsgChan
|
||||||
go receiveFdHandler(connLogFd, logCh, fdMsgChan)
|
go receiveFdHandler(connLogFd, logCh, fdMsgChan)
|
||||||
|
// receive fds from the querying Unix domain socket and send on queryMsgChan
|
||||||
go receiveQueryHandler(connQuery, logCh, queryMsgChan)
|
go receiveQueryHandler(connQuery, logCh, queryMsgChan)
|
||||||
|
// process both log messages and queries
|
||||||
go ringBufferHandler(linesInBuffer, linesInBuffer, logCh, queryMsgChan)
|
go ringBufferHandler(linesInBuffer, linesInBuffer, logCh, queryMsgChan)
|
||||||
|
|
||||||
doLog(logCh, "memlogd started")
|
doLog(logCh, "memlogd started")
|
||||||
|
|
||||||
for true {
|
loggingRequestHandler(lineMaxLength, logCh, fdMsgChan)
|
||||||
select {
|
|
||||||
case msg := <-fdMsgChan: // incoming fd
|
|
||||||
go readLogFromFd(lineMaxLength, msg.fd, msg.name, logCh)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -44,7 +45,7 @@ func TestFinite(t *testing.T) {
|
|||||||
for i := 0; i < 2*linesInBuffer; i++ {
|
for i := 0; i < 2*linesInBuffer; i++ {
|
||||||
logCh <- logEntry{time: time.Now(), source: "memlogd", msg: "hello TestFinite"}
|
logCh <- logEntry{time: time.Now(), source: "memlogd", msg: "hello TestFinite"}
|
||||||
}
|
}
|
||||||
a, b := socketpair()
|
a, b := loopback()
|
||||||
defer a.Close()
|
defer a.Close()
|
||||||
defer b.Close()
|
defer b.Close()
|
||||||
queryM := queryMessage{
|
queryM := queryMessage{
|
||||||
@ -85,7 +86,7 @@ func TestFinite2(t *testing.T) {
|
|||||||
logCh <- logEntry{time: time.Now(), source: "memlogd", msg: "hello TestFinite2"}
|
logCh <- logEntry{time: time.Now(), source: "memlogd", msg: "hello TestFinite2"}
|
||||||
}
|
}
|
||||||
|
|
||||||
a, b := socketpair()
|
a, b := loopback()
|
||||||
defer a.Close()
|
defer a.Close()
|
||||||
defer b.Close()
|
defer b.Close()
|
||||||
queryM := queryMessage{
|
queryM := queryMessage{
|
||||||
@ -112,22 +113,86 @@ func TestFinite2(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func socketpair() (net.Conn, net.Conn) {
|
func TestGoodName(t *testing.T) {
|
||||||
|
// Test that the source names can't contain ";"
|
||||||
|
linesInBuffer := 10
|
||||||
|
logCh := make(chan logEntry)
|
||||||
|
fdMsgChan := make(chan fdMessage)
|
||||||
|
queryMsgChan := make(chan queryMessage)
|
||||||
|
|
||||||
|
go ringBufferHandler(linesInBuffer, linesInBuffer, logCh, queryMsgChan)
|
||||||
|
go loggingRequestHandler(80, logCh, fdMsgChan)
|
||||||
|
|
||||||
fds, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
|
fds, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Unable to create socketpair: ", err)
|
log.Fatal("Unable to create socketpair: ", err)
|
||||||
}
|
}
|
||||||
f := os.NewFile(uintptr(fds[0]), "a")
|
a := fdToConn(fds[0])
|
||||||
a, err := net.FileConn(f)
|
b := fdToConn(fds[1])
|
||||||
if err != nil {
|
defer a.Close()
|
||||||
log.Fatal("Unable to create net.Conn from socketpair: ", err)
|
defer b.Close()
|
||||||
|
// defer close fds
|
||||||
|
|
||||||
|
fdMsgChan <- fdMessage{
|
||||||
|
name: "semi-colons are banned;",
|
||||||
|
fd: fds[0],
|
||||||
}
|
}
|
||||||
_ = f.Close()
|
// although the fd should be rejected my memlogd the Write should be buffered
|
||||||
f = os.NewFile(uintptr(fds[1]), "b")
|
// by the kernel and not block.
|
||||||
b, err := net.FileConn(f)
|
if _, err := b.Write([]byte("hello\n")); err != nil {
|
||||||
if err != nil {
|
log.Fatalf("Failed to write log message: %s", err)
|
||||||
log.Fatal("Unable to create net.Conn from socketpair: ", err)
|
}
|
||||||
|
c, d := loopback()
|
||||||
|
defer c.Close()
|
||||||
|
defer d.Close()
|
||||||
|
// this log should not be in the ring because the connection was rejected.
|
||||||
|
queryM := queryMessage{
|
||||||
|
conn: c,
|
||||||
|
mode: logDumpFollow,
|
||||||
|
}
|
||||||
|
queryMsgChan <- queryM
|
||||||
|
// The error log is generated asynchronously. It should be fast. On error time out
|
||||||
|
// after 5s.
|
||||||
|
d.SetDeadline(time.Now().Add(5 * time.Second))
|
||||||
|
r := bufio.NewReader(d)
|
||||||
|
for {
|
||||||
|
line, err := r.ReadString('\n')
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Unexpected error reading from socket: %s", err)
|
||||||
|
}
|
||||||
|
if strings.Contains(line, "ERROR: cannot register log") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Fatal("Failed to read error message when registering a log with a ;")
|
||||||
|
}
|
||||||
|
|
||||||
|
// caller must close fd themselves: closing the net.Conn will not close fd.
|
||||||
|
func fdToConn(fd int) net.Conn {
|
||||||
|
f := os.NewFile(uintptr(fd), "")
|
||||||
|
c, err := net.FileConn(f)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Unable to create net.Conn from file descriptor: ", err)
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func loopback() (net.Conn, net.Conn) {
|
||||||
|
fds, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Unable to create socketpair: ", err)
|
||||||
|
}
|
||||||
|
a := fdToConn(fds[0])
|
||||||
|
b := fdToConn(fds[1])
|
||||||
|
// net.Conns are independent of the fds, so we must close the fds now.
|
||||||
|
if err := syscall.Close(fds[0]); err != nil {
|
||||||
|
log.Fatal("Unable to close socketpair fd: ", err)
|
||||||
|
}
|
||||||
|
if err := syscall.Close(fds[1]); err != nil {
|
||||||
|
log.Fatal("Unable to close socketpair fd: ", err)
|
||||||
}
|
}
|
||||||
_ = f.Close()
|
|
||||||
return a, b
|
return a, b
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user