Merge branch 'master' into api

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>

Conflicts:
	MAINTAINERS
	cgroups/cgroups.go
	cgroups/fs/apply_raw.go
	cgroups/fs/notify_linux.go
	cgroups/fs/notify_linux_test.go
	cgroups/systemd/apply_systemd.go
	config.go
	configs/config_test.go
	console/console.go
	integration/exec_test.go
	integration/init_test.go
	integration/template_test.go
	integration/utils_test.go
	linux_notify.go
	linux_notify_test.go
	mount/init.go
	mount/mount_config.go
	mount/pivotroot.go
	mount/ptmx.go
	namespaces/create.go
	namespaces/exec.go
	namespaces/execin.go
	namespaces/init.go
	namespaces/nsenter/nsenter.c
	namespaces/nsenter/nsenter.go
	namespaces/utils.go
	network/network.go
	network/types.go
	network/veth.go
	notify_linux.go
	notify_linux_test.go
	nsinit/exec.go
	nsinit/main.go
	nsinit/nsenter.go
	nsinit/oom.go
	sample_configs/host-pid.json
	sample_configs/userns.json
	security/capabilities/capabilities.go
	update-vendor.sh
This commit is contained in:
Michael Crosby 2015-02-16 13:09:00 -08:00
commit f4cf808a3d
71 changed files with 4724 additions and 3479 deletions

View File

@ -3,4 +3,5 @@ Rohit Jnagal <jnagal@google.com> (@rjnagal)
Victor Marmol <vmarmol@google.com> (@vmarmol)
Mrunal Patel <mpatel@redhat.com> (@mrunalp)
Alexandr Morozov <lk4d4@docker.com> (@LK4D4)
Daniel, Dao Quang Minh <dqminh89@gmail.com> (@dqminh)
update-vendor.sh: Tianon Gravi <admwiggin@gmail.com> (@tianon)

View File

@ -12,7 +12,6 @@ sh:
GO_PACKAGES = $(shell find . -not \( -wholename ./vendor -prune -o -wholename ./.git -prune \) -name '*.go' -print0 | xargs -0n1 dirname | sort -u)
direct-test:
go get github.com/golang/glog && \
go test $(TEST_TAGS) -cover -v $(GO_PACKAGES)
direct-test-short:

View File

@ -24,7 +24,6 @@ func ApplyProfile(name string) error {
if name == "" {
return nil
}
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))

View File

@ -1,11 +1,11 @@
package fs
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"sync"
"github.com/docker/libcontainer/cgroups"
"github.com/docker/libcontainer/configs"
@ -40,20 +40,31 @@ type Manager struct {
}
// The absolute path to the root of the cgroup hierarchies.
var cgroupRootLock sync.Mutex
var cgroupRoot string
// TODO(vmarmol): Report error here, we'll probably need to wait for the new API.
func init() {
// Gets the cgroupRoot.
func getCgroupRoot() (string, error) {
cgroupRootLock.Lock()
defer cgroupRootLock.Unlock()
if cgroupRoot != "" {
return cgroupRoot, nil
}
// we can pick any subsystem to find the root
cpuRoot, err := cgroups.FindCgroupMountpoint("cpu")
if err != nil {
return
return "", err
}
cgroupRoot = filepath.Dir(cpuRoot)
root := filepath.Dir(cpuRoot)
if _, err := os.Stat(cgroupRoot); err != nil {
return
if _, err := os.Stat(root); err != nil {
return "", err
}
cgroupRoot = root
return cgroupRoot, nil
}
type data struct {
@ -172,8 +183,9 @@ func (m *Manager) GetPids() ([]int, error) {
}
func getCgroupData(c *configs.Cgroup, pid int) (*data, error) {
if cgroupRoot == "" {
return nil, fmt.Errorf("failed to find the cgroup root")
root, err := getCgroupRoot()
if err != nil {
return nil, err
}
cgroup := c.Name
@ -182,7 +194,7 @@ func getCgroupData(c *configs.Cgroup, pid int) (*data, error) {
}
return &data{
root: cgroupRoot,
root: root,
cgroup: cgroup,
c: c,
pid: pid,

View File

@ -30,9 +30,10 @@ type subsystem interface {
}
var (
connLock sync.Mutex
theConn *systemd.Conn
hasStartTransientUnit bool
connLock sync.Mutex
theConn *systemd.Conn
hasStartTransientUnit bool
hasTransientDefaultDependencies bool
)
func newProp(name string, units interface{}) systemd.Property {
@ -66,6 +67,18 @@ func UseSystemd() bool {
if dbusError, ok := err.(dbus.Error); ok {
if dbusError.Name == "org.freedesktop.DBus.Error.UnknownMethod" {
hasStartTransientUnit = false
return hasStartTransientUnit
}
}
}
// Assume StartTransientUnit on a scope allows DefaultDependencies
hasTransientDefaultDependencies = true
ddf := newProp("DefaultDependencies", false)
if _, err := theConn.StartTransientUnit("docker-systemd-test-default-dependencies.scope", "replace", ddf); err != nil {
if dbusError, ok := err.(dbus.Error); ok {
if dbusError.Name == "org.freedesktop.DBus.Error.PropertyReadOnly" {
hasTransientDefaultDependencies = false
}
}
}
@ -108,6 +121,11 @@ func (m *Manager) Apply(pid int) error {
newProp("CPUAccounting", true),
newProp("BlockIOAccounting", true))
if hasTransientDefaultDependencies {
properties = append(properties,
newProp("DefaultDependencies", false))
}
if c.Memory != 0 {
properties = append(properties,
newProp("MemoryLimit", uint64(c.Memory)))
@ -128,14 +146,12 @@ func (m *Manager) Apply(pid int) error {
return err
}
if !c.AllowAllDevices {
if err := joinDevices(c, pid); err != nil {
return err
}
if err := joinDevices(c, pid); err != nil {
return err
}
// -1 disables memorySwap
if c.MemorySwap >= 0 && (c.Memory != 0 || c.MemorySwap > 0) {
if c.MemorySwap >= 0 && c.Memory != 0 {
if err := joinMemory(c, pid); err != nil {
return err
}
@ -290,16 +306,16 @@ func joinDevices(c *configs.Cgroup, pid int) error {
return err
}
if err := writeFile(path, "devices.deny", "a"); err != nil {
return err
if !c.AllowAllDevices {
if err := writeFile(path, "devices.deny", "a"); err != nil {
return err
}
}
for _, dev := range c.AllowedDevices {
if err := writeFile(path, "devices.allow", dev.CgroupString()); err != nil {
return err
}
}
return nil
}

View File

@ -162,7 +162,7 @@ func TestHostUIDNoUSERNS(t *testing.T) {
t.Fatal(err)
}
if uid != 0 {
t.Fatal("expected uid 0 with no USERNS but received %d", uid)
t.Fatalf("expected uid 0 with no USERNS but received %d", uid)
}
}
@ -182,7 +182,7 @@ func TestHostUIDWithUSERNS(t *testing.T) {
t.Fatal(err)
}
if uid != 1000 {
t.Fatal("expected uid 1000 with no USERNS but received %d", uid)
t.Fatalf("expected uid 1000 with no USERNS but received %d", uid)
}
}
@ -195,7 +195,7 @@ func TestHostGIDNoUSERNS(t *testing.T) {
t.Fatal(err)
}
if uid != 0 {
t.Fatal("expected gid 0 with no USERNS but received %d", uid)
t.Fatalf("expected gid 0 with no USERNS but received %d", uid)
}
}
@ -215,6 +215,6 @@ func TestHostGIDWithUSERNS(t *testing.T) {
t.Fatal(err)
}
if uid != 1000 {
t.Fatal("expected gid 1000 with no USERNS but received %d", uid)
t.Fatalf("expected gid 1000 with no USERNS but received %d", uid)
}
}

38
docs/man/nsinit.1.md Normal file
View File

@ -0,0 +1,38 @@
% nsinit User Manual
% docker/libcontainer
% JAN 2015
NAME:
nsinit - A low-level utility for managing containers.
It is used to spawn new containers or join existing containers.
USAGE:
nsinit [global options] command [command options] [arguments...]
VERSION:
0.1
COMMANDS:
config display the container configuration
exec execute a new command inside a container
init runs the init process inside the namespace
oom display oom notifications for a container
pause pause the container's processes
stats display statistics for the container
unpause unpause the container's processes
help, h shows a list of commands or help for one command
EXAMPLES:
Get the <container_id> of an already running docker container.
`sudo docker ps` will return the list of all the running containers.
take the <container_id> (e.g. 4addb0b2d307) and go to its config directory
`/var/lib/docker/execdriver/native/4addb0b2d307` and here you can run the nsinit
command line utility.
e.g. `nsinit exec /bin/bash` will start a shell on the already running container.
# HISTORY
Jan 2015, Originally compiled by Shishir Mahajan (shishir dot mahajan at redhat dot com)
based on nsinit source material and internal work.

132
integration/execin_test.go Normal file
View File

@ -0,0 +1,132 @@
package integration
import (
"os"
"strings"
"syscall"
"testing"
"github.com/docker/libcontainer"
)
func TestExecIn(t *testing.T) {
if testing.Short() {
return
}
rootfs, err := newRootfs()
if err != nil {
t.Fatal(err)
}
defer remove(rootfs)
config := newTemplateConfig(rootfs)
container, err := newContainer(config)
if err != nil {
t.Fatal(err)
}
defer container.Destroy()
buffers := newStdBuffers()
process := &libcontainer.Process{
Args: []string{"sleep", "10"},
Env: standardEnvironment,
Stdin: buffers.Stdin,
Stdout: buffers.Stdout,
Stderr: buffers.Stderr,
}
pid1, err := container.Start(process)
if err != nil {
t.Fatal(err)
}
buffers = newStdBuffers()
psPid, err := container.Start(&libcontainer.Process{
Args: []string{"ps"},
Env: standardEnvironment,
Stdin: buffers.Stdin,
Stdout: buffers.Stdout,
Stderr: buffers.Stderr,
})
if err != nil {
t.Fatal(err)
}
ps, err := os.FindProcess(psPid)
if err != nil {
t.Fatal(err)
}
if _, err := ps.Wait(); err != nil {
t.Fatal(err)
}
p, err := os.FindProcess(pid1)
if err != nil {
t.Fatal(err)
}
if err := p.Signal(syscall.SIGKILL); err != nil {
t.Log(err)
}
if _, err := p.Wait(); err != nil {
t.Log(err)
}
out := buffers.Stdout.String()
if !strings.Contains(out, "sleep 10") || !strings.Contains(out, "ps") {
t.Fatalf("unexpected running process, output %q", out)
}
}
func TestExecInRlimit(t *testing.T) {
if testing.Short() {
return
}
rootfs, err := newRootfs()
if err != nil {
t.Fatal(err)
}
defer remove(rootfs)
config := newTemplateConfig(rootfs)
container, err := newContainer(config)
if err != nil {
t.Fatal(err)
}
defer container.Destroy()
buffers := newStdBuffers()
process := &libcontainer.Process{
Args: []string{"sleep", "10"},
Env: standardEnvironment,
Stdin: buffers.Stdin,
Stdout: buffers.Stdout,
Stderr: buffers.Stderr,
}
pid1, err := container.Start(process)
if err != nil {
t.Fatal(err)
}
buffers = newStdBuffers()
psPid, err := container.Start(&libcontainer.Process{
Args: []string{"/bin/sh", "-c", "ulimit -n"},
Env: standardEnvironment,
Stdin: buffers.Stdin,
Stdout: buffers.Stdout,
Stderr: buffers.Stderr,
})
if err != nil {
t.Fatal(err)
}
ps, err := os.FindProcess(psPid)
if err != nil {
t.Fatal(err)
}
if _, err := ps.Wait(); err != nil {
t.Fatal(err)
}
p, err := os.FindProcess(pid1)
if err != nil {
t.Fatal(err)
}
if err := p.Signal(syscall.SIGKILL); err != nil {
t.Log(err)
}
if _, err := p.Wait(); err != nil {
t.Log(err)
}
out := buffers.Stdout.String()
if limit := strings.TrimSpace(out); limit != "1024" {
t.Fatalf("expected rlimit to be 1024, got %s", limit)
}
}

View File

@ -21,6 +21,7 @@ func init() {
if err != nil {
log.Fatalf("unable to initialize for container: %s", err)
}
factory.StartInitialization(3)
os.Exit(1)
if err := factory.StartInitialization(3); err != nil {
log.Fatal(err)
}
}

View File

@ -67,11 +67,27 @@ func copyBusybox(dest string) error {
return nil
}
func newContainer(config *configs.Config) (libcontainer.Container, error) {
factory, err := libcontainer.New(".",
libcontainer.InitArgs(os.Args[0], "init", "--"),
libcontainer.Cgroupfs,
)
if err != nil {
return nil, err
}
return factory.Create("testCT", config)
}
// runContainer runs the container with the specific config and arguments
//
// buffers are returned containing the STDOUT and STDERR output for the run
// along with the exit code and any go error
func runContainer(config *configs.Config, console string, args ...string) (buffers *stdBuffers, exitCode int, err error) {
container, err := newContainer(config)
if err != nil {
return nil, -1, err
}
defer container.Destroy()
buffers = newStdBuffers()
process := &libcontainer.Process{
Args: args,
@ -81,32 +97,18 @@ func runContainer(config *configs.Config, console string, args ...string) (buffe
Stderr: buffers.Stderr,
}
factory, err := libcontainer.New(".", libcontainer.InitArgs(os.Args[0], "init", "--"), libcontainer.Cgroupfs)
if err != nil {
return nil, -1, err
}
container, err := factory.Create("testCT", config)
if err != nil {
return nil, -1, err
}
defer container.Destroy()
pid, err := container.Start(process)
if err != nil {
return nil, -1, err
}
p, err := os.FindProcess(pid)
if err != nil {
return nil, -1, err
}
ps, err := p.Wait()
if err != nil {
return nil, -1, err
}
status := ps.Sys().(syscall.WaitStatus)
if status.Exited() {
exitCode = status.ExitStatus()
@ -115,6 +117,5 @@ func runContainer(config *configs.Config, console string, args ...string) (buffe
} else {
return nil, -1, err
}
return
}

View File

@ -11,9 +11,9 @@ import (
"sync"
"syscall"
log "github.com/Sirupsen/logrus"
"github.com/docker/libcontainer/cgroups"
"github.com/docker/libcontainer/configs"
"github.com/golang/glog"
)
type linuxContainer struct {
@ -49,7 +49,6 @@ func (c *linuxContainer) State() (*State, error) {
}
func (c *linuxContainer) Processes() ([]int, error) {
glog.Info("fetch container processes")
pids, err := c.cgroupManager.GetPids()
if err != nil {
return nil, newSystemError(err)
@ -58,7 +57,6 @@ func (c *linuxContainer) Processes() ([]int, error) {
}
func (c *linuxContainer) Stats() (*Stats, error) {
glog.Info("fetch container stats")
var (
err error
stats = &Stats{}
@ -94,7 +92,7 @@ func (c *linuxContainer) Start(process *Process) (int, error) {
if err := parent.start(); err != nil {
// terminate the process to ensure that it properly is reaped.
if err := parent.terminate(); err != nil {
glog.Warning(err)
log.Warn(err)
}
return -1, newSystemError(err)
}
@ -207,7 +205,7 @@ func (c *linuxContainer) Destroy() error {
}
if !c.config.Namespaces.Contains(configs.NEWPID) {
if err := killCgroupProcesses(c.cgroupManager); err != nil {
glog.Warning(err)
log.Warn(err)
}
}
err = c.cgroupManager.Destroy()

View File

@ -9,13 +9,13 @@ import (
"strings"
"syscall"
log "github.com/Sirupsen/logrus"
"github.com/docker/libcontainer/cgroups"
"github.com/docker/libcontainer/configs"
"github.com/docker/libcontainer/netlink"
"github.com/docker/libcontainer/system"
"github.com/docker/libcontainer/user"
"github.com/docker/libcontainer/utils"
"github.com/golang/glog"
)
type initType string
@ -235,7 +235,7 @@ func setupRlimits(config *configs.Config) error {
func killCgroupProcesses(m cgroups.Manager) error {
var procs []*os.Process
if err := m.Freeze(configs.Frozen); err != nil {
glog.Warning(err)
log.Warn(err)
}
pids, err := m.GetPids()
if err != nil {
@ -246,16 +246,16 @@ func killCgroupProcesses(m cgroups.Manager) error {
if p, err := os.FindProcess(pid); err == nil {
procs = append(procs, p)
if err := p.Kill(); err != nil {
glog.Warning(err)
log.Warn(err)
}
}
}
if err := m.Freeze(configs.Thawed); err != nil {
glog.Warning(err)
log.Warn(err)
}
for _, p := range procs {
if _, err := p.Wait(); err != nil {
glog.Warning(err)
log.Warn(err)
}
}
return nil

View File

@ -10,9 +10,9 @@ import (
"os/exec"
"syscall"
log "github.com/Sirupsen/logrus"
"github.com/docker/libcontainer/cgroups"
"github.com/docker/libcontainer/system"
"github.com/golang/glog"
)
type parentProcess interface {
@ -82,7 +82,7 @@ func (p *setnsProcess) execSetns() (*os.Process, error) {
return nil, newSystemError(err)
}
if !status.Success() {
return nil, newSystemError(&exec.ExitError{status})
return nil, newSystemError(&exec.ExitError{ProcessState: status})
}
var pid *pid
if err := json.NewDecoder(p.parentPipe).Decode(&pid); err != nil {
@ -153,7 +153,7 @@ func (p *initProcess) start() error {
}
if err := parent.start(); err != nil {
if err := parent.terminate(); err != nil {
glog.Warning(err)
log.Warn(err)
}
return err
}

View File

@ -7,7 +7,6 @@ import (
"math/rand"
"net"
"os"
"path/filepath"
"sync/atomic"
"syscall"
"unsafe"
@ -23,6 +22,7 @@ const (
IFLA_VLAN_ID = 1
IFLA_NET_NS_FD = 28
IFLA_ADDRESS = 1
IFLA_BRPORT_MODE = 4
SIOC_BRADDBR = 0x89a0
SIOC_BRDELBR = 0x89a1
SIOC_BRADDIF = 0x89a2
@ -1253,25 +1253,33 @@ func SetMacAddress(name, addr string) error {
}
func SetHairpinMode(iface *net.Interface, enabled bool) error {
sysPath := filepath.Join("/sys/class/net", iface.Name, "brport/hairpin_mode")
sysFile, err := os.OpenFile(sysPath, os.O_WRONLY, 0)
s, err := getNetlinkSocket()
if err != nil {
return err
}
defer sysFile.Close()
defer s.Close()
req := newNetlinkRequest(syscall.RTM_SETLINK, syscall.NLM_F_ACK)
var writeVal []byte
msg := newIfInfomsg(syscall.AF_BRIDGE)
msg.Type = syscall.RTM_SETLINK
msg.Flags = syscall.NLM_F_REQUEST
msg.Index = int32(iface.Index)
msg.Change = DEFAULT_CHANGE
req.AddData(msg)
mode := []byte{0}
if enabled {
writeVal = []byte("1")
} else {
writeVal = []byte("0")
mode[0] = byte(1)
}
if _, err := sysFile.Write(writeVal); err != nil {
br := newRtAttr(syscall.IFLA_PROTINFO|syscall.NLA_F_NESTED, nil)
newRtAttrChild(br, IFLA_BRPORT_MODE, mode)
req.AddData(br)
if err := s.Send(req); err != nil {
return err
}
return nil
return s.HandleAck(req.Seq)
}
func ChangeName(iface *net.Interface, newName string) error {

View File

@ -5,6 +5,7 @@ import (
"os/signal"
"syscall"
log "github.com/Sirupsen/logrus"
"github.com/codegangsta/cli"
"github.com/docker/libcontainer"
"github.com/docker/libcontainer/utils"
@ -31,6 +32,7 @@ var execCommand = cli.Command{
}
func execAction(context *cli.Context) {
entry := log.WithField("parent", "nsinit")
factory, err := loadFactory(context)
if err != nil {
fatal(err)
@ -42,9 +44,7 @@ func execAction(context *cli.Context) {
created := false
container, err := factory.Load(context.String("id"))
if err != nil {
if lerr, ok := err.(libcontainer.Error); !ok || lerr.Code() != libcontainer.ContainerNotExists {
fatal(err)
}
entry.Debug("creating container")
config, err := loadConfig(context)
if err != nil {
tty.Close()
@ -53,6 +53,7 @@ func execAction(context *cli.Context) {
if tty.console != nil {
config.Console = tty.console.Path()
}
created = true
if container, err = factory.Create(context.String("id"), config); err != nil {
tty.Close()
@ -72,16 +73,25 @@ func execAction(context *cli.Context) {
pid, err := container.Start(process)
if err != nil {
tty.Close()
if created {
container.Destroy()
}
fatal(err)
}
proc, err := os.FindProcess(pid)
if err != nil {
tty.Close()
if created {
container.Destroy()
}
fatal(err)
}
status, err := proc.Wait()
if err != nil {
tty.Close()
if created {
container.Destroy()
}
fatal(err)
}
if created {

View File

@ -1,9 +1,9 @@
package main
import (
"log"
"runtime"
log "github.com/Sirupsen/logrus"
"github.com/codegangsta/cli"
"github.com/docker/libcontainer"
_ "github.com/docker/libcontainer/nsenter"
@ -13,6 +13,7 @@ var initCommand = cli.Command{
Name: "init",
Usage: "runs the init process inside the namespace",
Action: func(context *cli.Context) {
log.SetLevel(log.DebugLevel)
runtime.GOMAXPROCS(1)
runtime.LockOSThread()
factory, err := libcontainer.New("")

View File

@ -1,9 +1,9 @@
package main
import (
"log"
"os"
log "github.com/Sirupsen/logrus"
"github.com/codegangsta/cli"
)
@ -13,9 +13,9 @@ func main() {
app.Version = "2"
app.Author = "libcontainer maintainers"
app.Flags = []cli.Flag{
cli.StringFlag{Name: "nspid"},
cli.StringFlag{Name: "console"},
cli.StringFlag{Name: "root", Value: ".", Usage: "root directory for containers"},
cli.StringFlag{Name: "log-file", Value: "nsinit-debug.log", Usage: "set the log file to output logs to"},
cli.BoolFlag{Name: "debug", Usage: "enable debug output in the logs"},
}
app.Commands = []cli.Command{
configCommand,
@ -27,6 +27,19 @@ func main() {
unpauseCommand,
stateCommand,
}
app.Before = func(context *cli.Context) error {
if context.GlobalBool("debug") {
log.SetLevel(log.DebugLevel)
}
if path := context.GlobalString("log-file"); path != "" {
f, err := os.Create(path)
if err != nil {
return err
}
log.SetOutput(f)
}
return nil
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}

View File

@ -1,194 +1,341 @@
{
"capabilities": [
"CHOWN",
"DAC_OVERRIDE",
"FOWNER",
"MKNOD",
"NET_RAW",
"SETGID",
"SETUID",
"SETFCAP",
"SETPCAP",
"NET_BIND_SERVICE",
"SYS_CHROOT",
"KILL"
],
"cgroups": {
"allowed_devices": [
{
"permissions": "m",
"major": -1,
"minor": -1,
"type": 99
},
{
"permissions": "m",
"major": -1,
"minor": -1,
"type": 98
},
{
"permissions": "rwm",
"major": 5,
"minor": 1,
"path": "/dev/console",
"type": 99
},
{
"permissions": "rwm",
"major": 4,
"path": "/dev/tty0",
"type": 99
},
{
"permissions": "rwm",
"major": 4,
"minor": 1,
"path": "/dev/tty1",
"type": 99
},
{
"permissions": "rwm",
"major": 136,
"minor": -1,
"type": 99
},
{
"permissions": "rwm",
"major": 5,
"minor": 2,
"type": 99
},
{
"permissions": "rwm",
"major": 10,
"minor": 200,
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 3,
"path": "/dev/null",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 5,
"path": "/dev/zero",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 7,
"path": "/dev/full",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 5,
"path": "/dev/tty",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 9,
"path": "/dev/urandom",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 8,
"path": "/dev/random",
"type": 99
}
],
"name": "docker-koye",
"parent": "docker"
},
"restrict_sys": true,
"apparmor_profile": "docker-default",
"devices": [
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 3,
"path": "/dev/null",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 5,
"path": "/dev/zero",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 7,
"path": "/dev/full",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 5,
"path": "/dev/tty",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 9,
"path": "/dev/urandom",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 8,
"path": "/dev/random",
"type": 99
}
],
"environment": [
"HOME=/",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"HOSTNAME=koye",
"TERM=xterm"
],
"hostname": "koye",
"namespaces": [
{"type":"NEWIPC"},
{"type": "NEWNET"},
{"type": "NEWNS"},
{"type": "NEWPID"},
{"type": "NEWUTS"}
],
"networks": [
{
"address": "127.0.0.1/0",
"gateway": "localhost",
"mtu": 1500,
"type": "loopback"
}
],
"tty": true,
"user": "daemon"
"no_pivot_root": false,
"parent_death_signal": 0,
"pivot_dir": "",
"rootfs": "/rootfs/jessie",
"readonlyfs": false,
"mounts": [
{
"source": "shm",
"destination": "/dev/shm",
"device": "tmpfs",
"flags": 14,
"data": "mode=1777,size=65536k",
"relabel": ""
},
{
"source": "mqueue",
"destination": "/dev/mqueue",
"device": "mqueue",
"flags": 14,
"data": "",
"relabel": ""
},
{
"source": "sysfs",
"destination": "/sys",
"device": "sysfs",
"flags": 15,
"data": "",
"relabel": ""
}
],
"devices": [
{
"type": 99,
"path": "/dev/fuse",
"major": 10,
"minor": 229,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/null",
"major": 1,
"minor": 3,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/zero",
"major": 1,
"minor": 5,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/full",
"major": 1,
"minor": 7,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/tty",
"major": 5,
"minor": 0,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/urandom",
"major": 1,
"minor": 9,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/random",
"major": 1,
"minor": 8,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
}
],
"mount_label": "",
"hostname": "nsinit",
"console": "",
"namespaces": [
{
"type": "NEWNS",
"path": ""
},
{
"type": "NEWUTS",
"path": ""
},
{
"type": "NEWIPC",
"path": ""
},
{
"type": "NEWPID",
"path": ""
},
{
"type": "NEWNET",
"path": ""
}
],
"capabilities": [
"CHOWN",
"DAC_OVERRIDE",
"FSETID",
"FOWNER",
"MKNOD",
"NET_RAW",
"SETGID",
"SETUID",
"SETFCAP",
"SETPCAP",
"NET_BIND_SERVICE",
"SYS_CHROOT",
"KILL",
"AUDIT_WRITE"
],
"networks": [
{
"type": "loopback",
"name": "",
"bridge": "",
"mac_address": "",
"address": "127.0.0.1/0",
"gateway": "localhost",
"ipv6_address": "",
"ipv6_gateway": "",
"mtu": 0,
"txqueuelen": 0,
"host_interface_name": ""
}
],
"routes": null,
"cgroups": {
"name": "libcontainer",
"parent": "nsinit",
"allow_all_devices": false,
"allowed_devices": [
{
"type": 99,
"path": "",
"major": -1,
"minor": -1,
"permissions": "m",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 98,
"path": "",
"major": -1,
"minor": -1,
"permissions": "m",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/console",
"major": 5,
"minor": 1,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/tty0",
"major": 4,
"minor": 0,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/tty1",
"major": 4,
"minor": 1,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "",
"major": 136,
"minor": -1,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "",
"major": 5,
"minor": 2,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "",
"major": 10,
"minor": 200,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/null",
"major": 1,
"minor": 3,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/zero",
"major": 1,
"minor": 5,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/full",
"major": 1,
"minor": 7,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/tty",
"major": 5,
"minor": 0,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/urandom",
"major": 1,
"minor": 9,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/random",
"major": 1,
"minor": 8,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
}
],
"memory": 0,
"memory_reservation": 0,
"memory_swap": 0,
"cpu_shares": 0,
"cpu_quota": 0,
"cpu_period": 0,
"cpuset_cpus": "",
"cpuset_mems": "",
"blkio_weight": 0,
"freezer": "",
"slice": ""
},
"apparmor_profile": "docker-default",
"process_label": "",
"rlimits": [
{
"type": 7,
"hard": 1024,
"soft": 1024
}
],
"additional_groups": null,
"uid_mappings": null,
"gid_mappings": null,
"mask_paths": [
"/proc/kcore"
],
"readonly_paths": [
"/proc/sys",
"/proc/sysrq-trigger",
"/proc/irq",
"/proc/bus"
]
}

View File

@ -1,200 +1,354 @@
{
"capabilities": [
"CHOWN",
"DAC_OVERRIDE",
"FOWNER",
"MKNOD",
"NET_RAW",
"SETGID",
"SETUID",
"SETFCAP",
"SETPCAP",
"NET_BIND_SERVICE",
"SYS_CHROOT",
"KILL"
],
"cgroups": {
"allowed_devices": [
{
"permissions": "m",
"major": -1,
"minor": -1,
"type": 99
},
{
"permissions": "m",
"major": -1,
"minor": -1,
"type": 98
},
{
"permissions": "rwm",
"major": 5,
"minor": 1,
"path": "/dev/console",
"type": 99
},
{
"permissions": "rwm",
"major": 4,
"path": "/dev/tty0",
"type": 99
},
{
"permissions": "rwm",
"major": 4,
"minor": 1,
"path": "/dev/tty1",
"type": 99
},
{
"permissions": "rwm",
"major": 136,
"minor": -1,
"type": 99
},
{
"permissions": "rwm",
"major": 5,
"minor": 2,
"type": 99
},
{
"permissions": "rwm",
"major": 10,
"minor": 200,
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 3,
"path": "/dev/null",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 5,
"path": "/dev/zero",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 7,
"path": "/dev/full",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 5,
"path": "/dev/tty",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 9,
"path": "/dev/urandom",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 8,
"path": "/dev/random",
"type": 99
}
],
"name": "docker-koye",
"parent": "docker"
},
"restrict_sys": true,
"devices": [
"no_pivot_root": false,
"parent_death_signal": 0,
"pivot_dir": "",
"rootfs": "/rootfs/jessie",
"readonlyfs": false,
"mounts": [
{
"source": "shm",
"destination": "/dev/shm",
"device": "tmpfs",
"flags": 14,
"data": "mode=1777,size=65536k",
"relabel": ""
},
{
"source": "mqueue",
"destination": "/dev/mqueue",
"device": "mqueue",
"flags": 14,
"data": "",
"relabel": ""
},
{
"source": "sysfs",
"destination": "/sys",
"device": "sysfs",
"flags": 15,
"data": "",
"relabel": ""
}
],
"devices": [
{
"type": 99,
"path": "/dev/fuse",
"major": 10,
"minor": 229,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/null",
"major": 1,
"minor": 3,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/zero",
"major": 1,
"minor": 5,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/full",
"major": 1,
"minor": 7,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/tty",
"major": 5,
"minor": 0,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/urandom",
"major": 1,
"minor": 9,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/random",
"major": 1,
"minor": 8,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
}
],
"mount_label": "",
"hostname": "koye",
"console": "",
"namespaces": [
{
"type": "NEWNS",
"path": ""
},
{
"type": "NEWUTS",
"path": ""
},
{
"type": "NEWIPC",
"path": ""
},
{
"type": "NEWPID",
"path": ""
},
{
"type": "NEWNET",
"path": ""
}
],
"capabilities": [
"CHOWN",
"DAC_OVERRIDE",
"FSETID",
"FOWNER",
"MKNOD",
"NET_RAW",
"SETGID",
"SETUID",
"SETFCAP",
"SETPCAP",
"NET_BIND_SERVICE",
"SYS_CHROOT",
"KILL",
"AUDIT_WRITE"
],
"networks": [
{
"type": "loopback",
"name": "",
"bridge": "",
"mac_address": "",
"address": "127.0.0.1/0",
"gateway": "localhost",
"ipv6_address": "",
"ipv6_gateway": "",
"mtu": 0,
"txqueuelen": 0,
"host_interface_name": ""
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 3,
"path": "/dev/null",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 5,
"path": "/dev/zero",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 7,
"path": "/dev/full",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 5,
"path": "/dev/tty",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 9,
"path": "/dev/urandom",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 8,
"path": "/dev/random",
"type": 99
}
],
"environment": [
"HOME=/",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"HOSTNAME=koye",
"TERM=xterm"
],
"hostname": "koye",
"namespaces": [
{"type": "NEWIPC"},
{"type": "NEWNET"},
{"type": "NEWNS"},
{"type": "NEWPID"},
{"type": "NEWUTS"}
],
"networks": [
{
"address": "127.0.0.1/0",
"gateway": "localhost",
"mtu": 1500,
"type": "loopback"
},
{
"address": "172.17.0.101/16",
"bridge": "docker0",
"veth_prefix": "veth",
"gateway": "172.17.42.1",
"mtu": 1500,
"type": "veth"
}
],
"tty": true
"type": "veth",
"name": "eth0",
"bridge": "docker0",
"mac_address": "",
"address": "172.17.0.101/16",
"gateway": "172.17.42.1",
"ipv6_address": "",
"ipv6_gateway": "",
"mtu": 1500,
"txqueuelen": 0,
"host_interface_name": "vethnsinit"
}
],
"routes": null,
"cgroups": {
"name": "libcontainer",
"parent": "nsinit",
"allow_all_devices": false,
"allowed_devices": [
{
"type": 99,
"path": "",
"major": -1,
"minor": -1,
"permissions": "m",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 98,
"path": "",
"major": -1,
"minor": -1,
"permissions": "m",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/console",
"major": 5,
"minor": 1,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/tty0",
"major": 4,
"minor": 0,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/tty1",
"major": 4,
"minor": 1,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "",
"major": 136,
"minor": -1,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "",
"major": 5,
"minor": 2,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "",
"major": 10,
"minor": 200,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/null",
"major": 1,
"minor": 3,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/zero",
"major": 1,
"minor": 5,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/full",
"major": 1,
"minor": 7,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/tty",
"major": 5,
"minor": 0,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/urandom",
"major": 1,
"minor": 9,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/random",
"major": 1,
"minor": 8,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
}
],
"memory": 0,
"memory_reservation": 0,
"memory_swap": 0,
"cpu_shares": 0,
"cpu_quota": 0,
"cpu_period": 0,
"cpuset_cpus": "",
"cpuset_mems": "",
"blkio_weight": 0,
"freezer": "",
"slice": ""
},
"apparmor_profile": "",
"process_label": "",
"rlimits": [
{
"type": 7,
"hard": 1024,
"soft": 1024
}
],
"additional_groups": null,
"uid_mappings": null,
"gid_mappings": null,
"mask_paths": [
"/proc/kcore"
],
"readonly_paths": [
"/proc/sys",
"/proc/sysrq-trigger",
"/proc/irq",
"/proc/bus"
]
}

View File

@ -1,198 +1,337 @@
{
"capabilities": [
"CHOWN",
"DAC_OVERRIDE",
"FOWNER",
"MKNOD",
"NET_RAW",
"SETGID",
"SETUID",
"SETFCAP",
"SETPCAP",
"NET_BIND_SERVICE",
"SYS_CHROOT",
"KILL"
],
"cgroups": {
"allowed_devices": [
{
"permissions": "m",
"major": -1,
"minor": -1,
"type": 99
},
{
"permissions": "m",
"major": -1,
"minor": -1,
"type": 98
},
{
"permissions": "rwm",
"major": 5,
"minor": 1,
"path": "/dev/console",
"type": 99
},
{
"permissions": "rwm",
"major": 4,
"path": "/dev/tty0",
"type": 99
},
{
"permissions": "rwm",
"major": 4,
"minor": 1,
"path": "/dev/tty1",
"type": 99
},
{
"permissions": "rwm",
"major": 136,
"minor": -1,
"type": 99
},
{
"permissions": "rwm",
"major": 5,
"minor": 2,
"type": 99
},
{
"permissions": "rwm",
"major": 10,
"minor": 200,
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 3,
"path": "/dev/null",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 5,
"path": "/dev/zero",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 7,
"path": "/dev/full",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 5,
"path": "/dev/tty",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 9,
"path": "/dev/urandom",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 8,
"path": "/dev/random",
"type": 99
}
],
"name": "docker-koye",
"parent": "docker"
},
"restrict_sys": true,
"devices": [
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 3,
"path": "/dev/null",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 5,
"path": "/dev/zero",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 7,
"path": "/dev/full",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 5,
"path": "/dev/tty",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 9,
"path": "/dev/urandom",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 8,
"path": "/dev/random",
"type": 99
}
],
"mounts": [
{
"type": "tmpfs",
"destination": "/tmp"
}
],
"environment": [
"HOME=/",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"HOSTNAME=koye",
"TERM=xterm"
],
"hostname": "koye",
"namespaces": [
{"type": "NEWIPC"},
{"type": "NEWNET"},
{"type": "NEWNS"},
{"type": "NEWUTS"}
],
"networks": [
"no_pivot_root": false,
"parent_death_signal": 0,
"pivot_dir": "",
"rootfs": "/rootfs/jessie",
"readonlyfs": false,
"mounts": [
{
"source": "shm",
"destination": "/dev/shm",
"device": "tmpfs",
"flags": 14,
"data": "mode=1777,size=65536k",
"relabel": ""
},
{
"source": "mqueue",
"destination": "/dev/mqueue",
"device": "mqueue",
"flags": 14,
"data": "",
"relabel": ""
},
{
"source": "sysfs",
"destination": "/sys",
"device": "sysfs",
"flags": 15,
"data": "",
"relabel": ""
}
],
"devices": [
{
"type": 99,
"path": "/dev/fuse",
"major": 10,
"minor": 229,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/null",
"major": 1,
"minor": 3,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/zero",
"major": 1,
"minor": 5,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/full",
"major": 1,
"minor": 7,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/tty",
"major": 5,
"minor": 0,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/urandom",
"major": 1,
"minor": 9,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/random",
"major": 1,
"minor": 8,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
}
],
"mount_label": "",
"hostname": "nsinit",
"console": "",
"namespaces": [
{
"type": "NEWNS",
"path": ""
},
{
"type": "NEWUTS",
"path": ""
},
{
"type": "NEWIPC",
"path": ""
},
{
"address": "127.0.0.1/0",
"gateway": "localhost",
"mtu": 1500,
"type": "loopback"
}
],
"tty": true,
"user": "daemon"
"type": "NEWNET",
"path": ""
}
],
"capabilities": [
"CHOWN",
"DAC_OVERRIDE",
"FSETID",
"FOWNER",
"MKNOD",
"NET_RAW",
"SETGID",
"SETUID",
"SETFCAP",
"SETPCAP",
"NET_BIND_SERVICE",
"SYS_CHROOT",
"KILL",
"AUDIT_WRITE"
],
"networks": [
{
"type": "loopback",
"name": "",
"bridge": "",
"mac_address": "",
"address": "127.0.0.1/0",
"gateway": "localhost",
"ipv6_address": "",
"ipv6_gateway": "",
"mtu": 0,
"txqueuelen": 0,
"host_interface_name": ""
}
],
"routes": null,
"cgroups": {
"name": "libcontainer",
"parent": "nsinit",
"allow_all_devices": false,
"allowed_devices": [
{
"type": 99,
"path": "",
"major": -1,
"minor": -1,
"permissions": "m",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 98,
"path": "",
"major": -1,
"minor": -1,
"permissions": "m",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/console",
"major": 5,
"minor": 1,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/tty0",
"major": 4,
"minor": 0,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/tty1",
"major": 4,
"minor": 1,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "",
"major": 136,
"minor": -1,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "",
"major": 5,
"minor": 2,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "",
"major": 10,
"minor": 200,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/null",
"major": 1,
"minor": 3,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/zero",
"major": 1,
"minor": 5,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/full",
"major": 1,
"minor": 7,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/tty",
"major": 5,
"minor": 0,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/urandom",
"major": 1,
"minor": 9,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/random",
"major": 1,
"minor": 8,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
}
],
"memory": 0,
"memory_reservation": 0,
"memory_swap": 0,
"cpu_shares": 0,
"cpu_quota": 0,
"cpu_period": 0,
"cpuset_cpus": "",
"cpuset_mems": "",
"blkio_weight": 0,
"freezer": "",
"slice": ""
},
"apparmor_profile": "",
"process_label": "",
"rlimits": [
{
"type": 7,
"hard": 1024,
"soft": 1024
}
],
"additional_groups": null,
"uid_mappings": null,
"gid_mappings": null,
"mask_paths": [
"/proc/kcore"
],
"readonly_paths": [
"/proc/sys",
"/proc/sysrq-trigger",
"/proc/irq",
"/proc/bus"
]
}

View File

@ -1,199 +1,341 @@
{
"capabilities": [
"CHOWN",
"DAC_OVERRIDE",
"FOWNER",
"MKNOD",
"NET_RAW",
"SETGID",
"SETUID",
"SETFCAP",
"SETPCAP",
"NET_BIND_SERVICE",
"SYS_CHROOT",
"KILL"
],
"cgroups": {
"allowed_devices": [
{
"permissions": "m",
"major": -1,
"minor": -1,
"type": 99
},
{
"permissions": "m",
"major": -1,
"minor": -1,
"type": 98
},
{
"permissions": "rwm",
"major": 5,
"minor": 1,
"path": "/dev/console",
"type": 99
},
{
"permissions": "rwm",
"major": 4,
"path": "/dev/tty0",
"type": 99
},
{
"permissions": "rwm",
"major": 4,
"minor": 1,
"path": "/dev/tty1",
"type": 99
},
{
"permissions": "rwm",
"major": 136,
"minor": -1,
"type": 99
},
{
"permissions": "rwm",
"major": 5,
"minor": 2,
"type": 99
},
{
"permissions": "rwm",
"major": 10,
"minor": 200,
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 3,
"path": "/dev/null",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 5,
"path": "/dev/zero",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 7,
"path": "/dev/full",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 5,
"path": "/dev/tty",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 9,
"path": "/dev/urandom",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 8,
"path": "/dev/random",
"type": 99
}
],
"name": "docker-koye",
"parent": "docker"
},
"restrict_sys": true,
"devices": [
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 3,
"path": "/dev/null",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 5,
"path": "/dev/zero",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 7,
"path": "/dev/full",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 5,
"path": "/dev/tty",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 9,
"path": "/dev/urandom",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 8,
"path": "/dev/random",
"type": 99
}
],
"mounts": [
{
"type": "tmpfs",
"destination": "/tmp"
}
],
"environment": [
"HOME=/",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"HOSTNAME=koye",
"TERM=xterm"
],
"hostname": "koye",
"namespaces": [
{"type": "NEWIPC"},
{"type": "NEWNET"},
{"type": "NEWNS"},
{"type": "NEWPID"},
{"type": "NEWUTS"}
],
"networks": [
{
"address": "127.0.0.1/0",
"gateway": "localhost",
"mtu": 1500,
"type": "loopback"
}
],
"tty": true,
"user": "daemon"
}
"no_pivot_root": false,
"parent_death_signal": 0,
"pivot_dir": "",
"rootfs": "/home/michael/development/gocode/src/github.com/docker/libcontainer",
"readonlyfs": false,
"mounts": [
{
"source": "shm",
"destination": "/dev/shm",
"device": "tmpfs",
"flags": 14,
"data": "mode=1777,size=65536k",
"relabel": ""
},
{
"source": "mqueue",
"destination": "/dev/mqueue",
"device": "mqueue",
"flags": 14,
"data": "",
"relabel": ""
},
{
"source": "sysfs",
"destination": "/sys",
"device": "sysfs",
"flags": 15,
"data": "",
"relabel": ""
}
],
"devices": [
{
"type": 99,
"path": "/dev/fuse",
"major": 10,
"minor": 229,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/null",
"major": 1,
"minor": 3,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/zero",
"major": 1,
"minor": 5,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/full",
"major": 1,
"minor": 7,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/tty",
"major": 5,
"minor": 0,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/urandom",
"major": 1,
"minor": 9,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/random",
"major": 1,
"minor": 8,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
}
],
"mount_label": "",
"hostname": "nsinit",
"console": "",
"namespaces": [
{
"type": "NEWNS",
"path": ""
},
{
"type": "NEWUTS",
"path": ""
},
{
"type": "NEWIPC",
"path": ""
},
{
"type": "NEWPID",
"path": ""
},
{
"type": "NEWNET",
"path": ""
}
],
"capabilities": [
"CHOWN",
"DAC_OVERRIDE",
"FSETID",
"FOWNER",
"MKNOD",
"NET_RAW",
"SETGID",
"SETUID",
"SETFCAP",
"SETPCAP",
"NET_BIND_SERVICE",
"SYS_CHROOT",
"KILL",
"AUDIT_WRITE"
],
"networks": [
{
"type": "loopback",
"name": "",
"bridge": "",
"mac_address": "",
"address": "127.0.0.1/0",
"gateway": "localhost",
"ipv6_address": "",
"ipv6_gateway": "",
"mtu": 0,
"txqueuelen": 0,
"host_interface_name": ""
}
],
"routes": null,
"cgroups": {
"name": "libcontainer",
"parent": "nsinit",
"allow_all_devices": false,
"allowed_devices": [
{
"type": 99,
"path": "",
"major": -1,
"minor": -1,
"permissions": "m",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 98,
"path": "",
"major": -1,
"minor": -1,
"permissions": "m",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/console",
"major": 5,
"minor": 1,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/tty0",
"major": 4,
"minor": 0,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/tty1",
"major": 4,
"minor": 1,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "",
"major": 136,
"minor": -1,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "",
"major": 5,
"minor": 2,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "",
"major": 10,
"minor": 200,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/null",
"major": 1,
"minor": 3,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/zero",
"major": 1,
"minor": 5,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/full",
"major": 1,
"minor": 7,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/tty",
"major": 5,
"minor": 0,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/urandom",
"major": 1,
"minor": 9,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/random",
"major": 1,
"minor": 8,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
}
],
"memory": 0,
"memory_reservation": 0,
"memory_swap": 0,
"cpu_shares": 0,
"cpu_quota": 0,
"cpu_period": 0,
"cpuset_cpus": "",
"cpuset_mems": "",
"blkio_weight": 0,
"freezer": "",
"slice": ""
},
"apparmor_profile": "",
"process_label": "",
"rlimits": [
{
"type": 7,
"hard": 1024,
"soft": 1024
}
],
"additional_groups": null,
"uid_mappings": null,
"gid_mappings": null,
"mask_paths": [
"/proc/kcore"
],
"readonly_paths": [
"/proc/sys",
"/proc/sysrq-trigger",
"/proc/irq",
"/proc/bus"
]
}

View File

@ -1,207 +0,0 @@
{
"capabilities": [
"CHOWN",
"DAC_OVERRIDE",
"FOWNER",
"MKNOD",
"NET_RAW",
"SETGID",
"SETUID",
"SETFCAP",
"SETPCAP",
"NET_BIND_SERVICE",
"SYS_CHROOT",
"KILL"
],
"cgroups": {
"allowed_devices": [
{
"permissions": "m",
"major": -1,
"minor": -1,
"type": 99
},
{
"permissions": "m",
"major": -1,
"minor": -1,
"type": 98
},
{
"permissions": "rwm",
"major": 5,
"minor": 1,
"path": "/dev/console",
"type": 99
},
{
"permissions": "rwm",
"major": 4,
"path": "/dev/tty0",
"type": 99
},
{
"permissions": "rwm",
"major": 4,
"minor": 1,
"path": "/dev/tty1",
"type": 99
},
{
"permissions": "rwm",
"major": 136,
"minor": -1,
"type": 99
},
{
"permissions": "rwm",
"major": 5,
"minor": 2,
"type": 99
},
{
"permissions": "rwm",
"major": 10,
"minor": 200,
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 3,
"path": "/dev/null",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 5,
"path": "/dev/zero",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 7,
"path": "/dev/full",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 5,
"path": "/dev/tty",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 9,
"path": "/dev/urandom",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 8,
"path": "/dev/random",
"type": 99
}
],
"name": "docker-koye",
"parent": "docker"
},
"restrict_sys": true,
"devices": [
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 3,
"path": "/dev/null",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 5,
"path": "/dev/zero",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 7,
"path": "/dev/full",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 5,
"path": "/dev/tty",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 9,
"path": "/dev/urandom",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 8,
"path": "/dev/random",
"type": 99
}
],
"environment": [
"HOME=/",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"HOSTNAME=koye",
"TERM=xterm"
],
"hostname": "koye",
"namespaces": [
{"type": "NEWIPC"},
{"type": "NEWNET"},
{"type": "NEWNS"},
{"type": "NEWPID"},
{"type": "NEWUTS"}
],
"networks": [
{
"address": "127.0.0.1/0",
"gateway": "localhost",
"mtu": 1500,
"type": "loopback"
},
{
"address": "172.17.0.101/16",
"bridge": "docker0",
"veth_prefix": "veth",
"mtu": 1500,
"type": "veth"
}
],
"routes": [
{
"destination": "0.0.0.0/0",
"source": "172.17.0.101",
"gateway": "172.17.42.1",
"interface_name": "eth0"
}
],
"tty": true
}

View File

@ -1,195 +1,341 @@
{
"capabilities": [
"CHOWN",
"DAC_OVERRIDE",
"FOWNER",
"MKNOD",
"NET_RAW",
"SETGID",
"SETUID",
"SETFCAP",
"SETPCAP",
"NET_BIND_SERVICE",
"SYS_CHROOT",
"KILL"
],
"cgroups": {
"allowed_devices": [
{
"permissions": "m",
"major": -1,
"minor": -1,
"type": 99
},
{
"permissions": "m",
"major": -1,
"minor": -1,
"type": 98
},
{
"permissions": "rwm",
"major": 5,
"minor": 1,
"path": "/dev/console",
"type": 99
},
{
"permissions": "rwm",
"major": 4,
"path": "/dev/tty0",
"type": 99
},
{
"permissions": "rwm",
"major": 4,
"minor": 1,
"path": "/dev/tty1",
"type": 99
},
{
"permissions": "rwm",
"major": 136,
"minor": -1,
"type": 99
},
{
"permissions": "rwm",
"major": 5,
"minor": 2,
"type": 99
},
{
"permissions": "rwm",
"major": 10,
"minor": 200,
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 3,
"path": "/dev/null",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 5,
"path": "/dev/zero",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 7,
"path": "/dev/full",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 5,
"path": "/dev/tty",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 9,
"path": "/dev/urandom",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 8,
"path": "/dev/random",
"type": 99
}
],
"name": "docker-koye",
"parent": "docker"
},
"restrict_sys": true,
"process_label": "system_u:system_r:svirt_lxc_net_t:s0:c164,c475",
"mount_label": "system_u:system_r:svirt_lxc_net_t:s0:c164,c475",
"devices": [
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 3,
"path": "/dev/null",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 5,
"path": "/dev/zero",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 7,
"path": "/dev/full",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 5,
"path": "/dev/tty",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 9,
"path": "/dev/urandom",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 8,
"path": "/dev/random",
"type": 99
}
],
"environment": [
"HOME=/",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"HOSTNAME=koye",
"TERM=xterm"
],
"hostname": "koye",
"namespaces": [
{"type": "NEWIPC"},
{"type": "NEWNET"},
{"type": "NEWNS"},
{"type": "NEWPID"},
{"type": "NEWUTS"}
],
"networks": [
{
"address": "127.0.0.1/0",
"gateway": "localhost",
"mtu": 1500,
"type": "loopback"
}
],
"tty": true,
"user": "daemon"
"no_pivot_root": false,
"parent_death_signal": 0,
"pivot_dir": "",
"rootfs": "/rootfs/jessie",
"readonlyfs": false,
"mounts": [
{
"source": "shm",
"destination": "/dev/shm",
"device": "tmpfs",
"flags": 14,
"data": "mode=1777,size=65536k",
"relabel": ""
},
{
"source": "mqueue",
"destination": "/dev/mqueue",
"device": "mqueue",
"flags": 14,
"data": "",
"relabel": ""
},
{
"source": "sysfs",
"destination": "/sys",
"device": "sysfs",
"flags": 15,
"data": "",
"relabel": ""
}
],
"devices": [
{
"type": 99,
"path": "/dev/fuse",
"major": 10,
"minor": 229,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/null",
"major": 1,
"minor": 3,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/zero",
"major": 1,
"minor": 5,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/full",
"major": 1,
"minor": 7,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/tty",
"major": 5,
"minor": 0,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/urandom",
"major": 1,
"minor": 9,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/random",
"major": 1,
"minor": 8,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
}
],
"mount_label": "system_u:system_r:svirt_lxc_net_t:s0:c164,c475",
"hostname": "nsinit",
"console": "",
"namespaces": [
{
"type": "NEWNS",
"path": ""
},
{
"type": "NEWUTS",
"path": ""
},
{
"type": "NEWIPC",
"path": ""
},
{
"type": "NEWPID",
"path": ""
},
{
"type": "NEWNET",
"path": ""
}
],
"capabilities": [
"CHOWN",
"DAC_OVERRIDE",
"FSETID",
"FOWNER",
"MKNOD",
"NET_RAW",
"SETGID",
"SETUID",
"SETFCAP",
"SETPCAP",
"NET_BIND_SERVICE",
"SYS_CHROOT",
"KILL",
"AUDIT_WRITE"
],
"networks": [
{
"type": "loopback",
"name": "",
"bridge": "",
"mac_address": "",
"address": "127.0.0.1/0",
"gateway": "localhost",
"ipv6_address": "",
"ipv6_gateway": "",
"mtu": 0,
"txqueuelen": 0,
"host_interface_name": ""
}
],
"routes": null,
"cgroups": {
"name": "libcontainer",
"parent": "nsinit",
"allow_all_devices": false,
"allowed_devices": [
{
"type": 99,
"path": "",
"major": -1,
"minor": -1,
"permissions": "m",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 98,
"path": "",
"major": -1,
"minor": -1,
"permissions": "m",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/console",
"major": 5,
"minor": 1,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/tty0",
"major": 4,
"minor": 0,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/tty1",
"major": 4,
"minor": 1,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "",
"major": 136,
"minor": -1,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "",
"major": 5,
"minor": 2,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "",
"major": 10,
"minor": 200,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/null",
"major": 1,
"minor": 3,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/zero",
"major": 1,
"minor": 5,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/full",
"major": 1,
"minor": 7,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/tty",
"major": 5,
"minor": 0,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/urandom",
"major": 1,
"minor": 9,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/random",
"major": 1,
"minor": 8,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
}
],
"memory": 0,
"memory_reservation": 0,
"memory_swap": 0,
"cpu_shares": 0,
"cpu_quota": 0,
"cpu_period": 0,
"cpuset_cpus": "",
"cpuset_mems": "",
"blkio_weight": 0,
"freezer": "",
"slice": ""
},
"apparmor_profile": "",
"process_label": "system_u:system_r:svirt_lxc_net_t:s0:c164,c475",
"rlimits": [
{
"type": 7,
"hard": 1024,
"soft": 1024
}
],
"additional_groups": null,
"uid_mappings": null,
"gid_mappings": null,
"mask_paths": [
"/proc/kcore"
],
"readonly_paths": [
"/proc/sys",
"/proc/sysrq-trigger",
"/proc/irq",
"/proc/bus"
]
}

View File

@ -1,249 +1,377 @@
{
"capabilities": [
"CHOWN",
"DAC_OVERRIDE",
"FOWNER",
"MKNOD",
"NET_RAW",
"SETGID",
"SETUID",
"SETFCAP",
"SETPCAP",
"NET_BIND_SERVICE",
"SYS_CHROOT",
"KILL"
],
"cgroups": {
"allowed_devices": [
{
"permissions": "m",
"major": -1,
"minor": -1,
"type": 99
},
{
"permissions": "m",
"major": -1,
"minor": -1,
"type": 98
},
{
"permissions": "rwm",
"major": 5,
"minor": 1,
"path": "/dev/console",
"type": 99
},
{
"permissions": "rwm",
"major": 4,
"path": "/dev/tty0",
"type": 99
},
{
"permissions": "rwm",
"major": 4,
"minor": 1,
"path": "/dev/tty1",
"type": 99
},
{
"permissions": "rwm",
"major": 136,
"minor": -1,
"type": 99
},
{
"permissions": "rwm",
"major": 5,
"minor": 2,
"type": 99
},
{
"permissions": "rwm",
"major": 10,
"minor": 200,
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 3,
"path": "/dev/null",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 5,
"path": "/dev/zero",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 7,
"path": "/dev/full",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 5,
"path": "/dev/tty",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 9,
"path": "/dev/urandom",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 8,
"path": "/dev/random",
"type": 99
}
],
"name": "docker-koye",
"parent": "docker"
},
"restrict_sys": true,
"devices": [
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 3,
"path": "/dev/null",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 5,
"path": "/dev/zero",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 7,
"path": "/dev/full",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 5,
"path": "/dev/tty",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 9,
"path": "/dev/urandom",
"type": 99
},
{
"permissions": "rwm",
"file_mode": 438,
"major": 1,
"minor": 8,
"path": "/dev/random",
"type": 99
}
],
"mounts": [
{
"type": "tmpfs",
"destination": "/tmp"
}
],
"environment": [
"HOME=/",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"HOSTNAME=koye",
"TERM=xterm"
],
"hostname": "koye",
"namespaces": [
{"type": "NEWIPC"},
{"type": "NEWNET"},
{"type": "NEWNS"},
{"type": "NEWPID"},
{"type": "NEWUTS"},
{"type": "NEWUSER"}
],
"networks": [
{
"address": "127.0.0.1/0",
"gateway": "localhost",
"mtu": 1500,
"type": "loopback"
},
{
"address": "172.17.0.9/16",
"gateway": "172.17.42.1",
"bridge": "docker0",
"veth_prefix": "veth",
"mtu": 1500,
"type": "veth"
}
],
"tty": true,
"user": "root",
"uid_mappings": [
{
"container_id": 0,
"host_id": 1000,
"size": 1
},
{
"container_id": 1,
"host_id": 1,
"size": 999
},
{
"container_id": 1001,
"host_id": 1001,
"size": 9000
}
],
"gid_mappings": [
{
"container_id": 0,
"host_id": 1000,
"size": 1
},
{
"container_id": 1,
"host_id": 1,
"size": 999
},
{
"container_id": 1001,
"host_id": 1001,
"size": 9000
}
],
"rlimits": [
{
"type": 7,
"hard": 999,
"soft": 999
}
]
"no_pivot_root": false,
"parent_death_signal": 0,
"pivot_dir": "",
"rootfs": "/rootfs/jessie",
"readonlyfs": false,
"mounts": [
{
"source": "shm",
"destination": "/dev/shm",
"device": "tmpfs",
"flags": 14,
"data": "mode=1777,size=65536k",
"relabel": ""
},
{
"source": "mqueue",
"destination": "/dev/mqueue",
"device": "mqueue",
"flags": 14,
"data": "",
"relabel": ""
},
{
"source": "sysfs",
"destination": "/sys",
"device": "sysfs",
"flags": 15,
"data": "",
"relabel": ""
}
],
"devices": [
{
"type": 99,
"path": "/dev/fuse",
"major": 10,
"minor": 229,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/null",
"major": 1,
"minor": 3,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/zero",
"major": 1,
"minor": 5,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/full",
"major": 1,
"minor": 7,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/tty",
"major": 5,
"minor": 0,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/urandom",
"major": 1,
"minor": 9,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/random",
"major": 1,
"minor": 8,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
}
],
"mount_label": "",
"hostname": "nsinit",
"console": "",
"namespaces": [
{
"type": "NEWNS",
"path": ""
},
{
"type": "NEWUTS",
"path": ""
},
{
"type": "NEWIPC",
"path": ""
},
{
"type": "NEWPID",
"path": ""
},
{
"type": "NEWNET",
"path": ""
},
{
"type": "NEWUSER",
"path": ""
}
],
"capabilities": [
"CHOWN",
"DAC_OVERRIDE",
"FSETID",
"FOWNER",
"MKNOD",
"NET_RAW",
"SETGID",
"SETUID",
"SETFCAP",
"SETPCAP",
"NET_BIND_SERVICE",
"SYS_CHROOT",
"KILL",
"AUDIT_WRITE"
],
"networks": [
{
"type": "loopback",
"name": "",
"bridge": "",
"mac_address": "",
"address": "127.0.0.1/0",
"gateway": "localhost",
"ipv6_address": "",
"ipv6_gateway": "",
"mtu": 0,
"txqueuelen": 0,
"host_interface_name": ""
}
],
"routes": null,
"cgroups": {
"name": "libcontainer",
"parent": "nsinit",
"allow_all_devices": false,
"allowed_devices": [
{
"type": 99,
"path": "",
"major": -1,
"minor": -1,
"permissions": "m",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 98,
"path": "",
"major": -1,
"minor": -1,
"permissions": "m",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/console",
"major": 5,
"minor": 1,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/tty0",
"major": 4,
"minor": 0,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/tty1",
"major": 4,
"minor": 1,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "",
"major": 136,
"minor": -1,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "",
"major": 5,
"minor": 2,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "",
"major": 10,
"minor": 200,
"permissions": "rwm",
"file_mode": 0,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/null",
"major": 1,
"minor": 3,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/zero",
"major": 1,
"minor": 5,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/full",
"major": 1,
"minor": 7,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/tty",
"major": 5,
"minor": 0,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/urandom",
"major": 1,
"minor": 9,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
},
{
"type": 99,
"path": "/dev/random",
"major": 1,
"minor": 8,
"permissions": "rwm",
"file_mode": 438,
"uid": 0,
"gid": 0
}
],
"memory": 0,
"memory_reservation": 0,
"memory_swap": 0,
"cpu_shares": 0,
"cpu_quota": 0,
"cpu_period": 0,
"cpuset_cpus": "",
"cpuset_mems": "",
"blkio_weight": 0,
"freezer": "",
"slice": ""
},
"apparmor_profile": "",
"process_label": "",
"rlimits": [
{
"type": 7,
"hard": 1024,
"soft": 1024
}
],
"additional_groups": null,
"uid_mappings": [
{
"container_id": 0,
"host_id": 1000,
"size": 1
},
{
"container_id": 1,
"host_id": 1,
"size": 999
},
{
"container_id": 1001,
"host_id": 1001,
"size": 2147482647
}
],
"gid_mappings": [
{
"container_id": 0,
"host_id": 1000,
"size": 1
},
{
"container_id": 1,
"host_id": 1,
"size": 999
},
{
"container_id": 1001,
"host_id": 1001,
"size": 2147482647
}
],
"mask_paths": [
"/proc/kcore"
],
"readonly_paths": [
"/proc/sys",
"/proc/sysrq-trigger",
"/proc/irq",
"/proc/bus"
]
}

View File

@ -42,8 +42,8 @@ clone() {
# the following lines are in sorted order, FYI
clone git github.com/codegangsta/cli 1.1.0
clone git github.com/coreos/go-systemd v2
clone git github.com/godbus/dbus v1
clone git github.com/syndtr/gocapability 3c85049eae
clone git github.com/golang/glog 44145f04b68c
clone git github.com/godbus/dbus v2
clone git github.com/Sirupsen/logrus v0.6.0
clone git github.com/syndtr/gocapability 1cf3ac4dc4
# intentionally not vendoring Docker itself... that'd be a circle :)

View File

@ -0,0 +1 @@
logrus

View File

@ -0,0 +1,9 @@
language: go
go:
- 1.2
- 1.3
- tip
install:
- go get github.com/stretchr/testify
- go get github.com/stvp/go-udp-testing
- go get github.com/tobi/airbrake-go

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Simon Eskildsen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,342 @@
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/>&nbsp;[![Build Status](https://travis-ci.org/Sirupsen/logrus.svg?branch=master)](https://travis-ci.org/Sirupsen/logrus)
Logrus is a structured logger for Go (golang), completely API compatible with
the standard library logger. [Godoc][godoc]. **Please note the Logrus API is not
yet stable (pre 1.0), the core API is unlikely change much but please version
control your Logrus to make sure you aren't fetching latest `master` on every
build.**
Nicely color-coded in development (when a TTY is attached, otherwise just
plain text):
![Colored](http://i.imgur.com/PY7qMwd.png)
With `log.Formatter = new(logrus.JSONFormatter)`, for easy parsing by logstash
or Splunk:
```json
{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the
ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
{"level":"warning","msg":"The group's number increased tremendously!",
"number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"}
{"animal":"walrus","level":"info","msg":"A giant walrus appears!",
"size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"}
{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.",
"size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"}
{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true,
"time":"2014-03-10 19:57:38.562543128 -0400 EDT"}
```
With the default `log.Formatter = new(logrus.TextFormatter)` when a TTY is not
attached, the output is compatible with the
[l2met](http://r.32k.io/l2met-introduction) format:
```text
time="2014-04-20 15:36:23.830442383 -0400 EDT" level="info" msg="A group of walrus emerges from the ocean" animal="walrus" size=10
time="2014-04-20 15:36:23.830584199 -0400 EDT" level="warning" msg="The group's number increased tremendously!" omg=true number=122
time="2014-04-20 15:36:23.830596521 -0400 EDT" level="info" msg="A giant walrus appears!" animal="walrus" size=10
time="2014-04-20 15:36:23.830611837 -0400 EDT" level="info" msg="Tremendously sized cow enters the ocean." animal="walrus" size=9
time="2014-04-20 15:36:23.830626464 -0400 EDT" level="fatal" msg="The ice breaks!" omg=true number=100
```
#### Example
The simplest way to use Logrus is simply the package-level exported logger:
```go
package main
import (
log "github.com/Sirupsen/logrus"
)
func main() {
log.WithFields(log.Fields{
"animal": "walrus",
}).Info("A walrus appears")
}
```
Note that it's completely api-compatible with the stdlib logger, so you can
replace your `log` imports everywhere with `log "github.com/Sirupsen/logrus"`
and you'll now have the flexibility of Logrus. You can customize it all you
want:
```go
package main
import (
"os"
log "github.com/Sirupsen/logrus"
"github.com/Sirupsen/logrus/hooks/airbrake"
)
func init() {
// Log as JSON instead of the default ASCII formatter.
log.SetFormatter(&log.JSONFormatter{})
// Use the Airbrake hook to report errors that have Error severity or above to
// an exception tracker. You can create custom hooks, see the Hooks section.
log.AddHook(&logrus_airbrake.AirbrakeHook{})
// Output to stderr instead of stdout, could also be a file.
log.SetOutput(os.Stderr)
// Only log the warning severity or above.
log.SetLevel(log.WarnLevel)
}
func main() {
log.WithFields(log.Fields{
"animal": "walrus",
"size": 10,
}).Info("A group of walrus emerges from the ocean")
log.WithFields(log.Fields{
"omg": true,
"number": 122,
}).Warn("The group's number increased tremendously!")
log.WithFields(log.Fields{
"omg": true,
"number": 100,
}).Fatal("The ice breaks!")
}
```
For more advanced usage such as logging to multiple locations from the same
application, you can also create an instance of the `logrus` Logger:
```go
package main
import (
"github.com/Sirupsen/logrus"
)
// Create a new instance of the logger. You can have any number of instances.
var log = logrus.New()
func main() {
// The API for setting attributes is a little different than the package level
// exported logger. See Godoc.
log.Out = os.Stderr
log.WithFields(logrus.Fields{
"animal": "walrus",
"size": 10,
}).Info("A group of walrus emerges from the ocean")
}
```
#### Fields
Logrus encourages careful, structured logging though logging fields instead of
long, unparseable error messages. For example, instead of: `log.Fatalf("Failed
to send event %s to topic %s with key %d")`, you should log the much more
discoverable:
```go
log.WithFields(log.Fields{
"event": event,
"topic": topic,
"key": key,
}).Fatal("Failed to send event")
```
We've found this API forces you to think about logging in a way that produces
much more useful logging messages. We've been in countless situations where just
a single added field to a log statement that was already there would've saved us
hours. The `WithFields` call is optional.
In general, with Logrus using any of the `printf`-family functions should be
seen as a hint you should add a field, however, you can still use the
`printf`-family functions with Logrus.
#### Hooks
You can add hooks for logging levels. For example to send errors to an exception
tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to
multiple places simultaneously, e.g. syslog.
```go
// Not the real implementation of the Airbrake hook. Just a simple sample.
import (
log "github.com/Sirupsen/logrus"
)
func init() {
log.AddHook(new(AirbrakeHook))
}
type AirbrakeHook struct{}
// `Fire()` takes the entry that the hook is fired for. `entry.Data[]` contains
// the fields for the entry. See the Fields section of the README.
func (hook *AirbrakeHook) Fire(entry *logrus.Entry) error {
err := airbrake.Notify(entry.Data["error"].(error))
if err != nil {
log.WithFields(log.Fields{
"source": "airbrake",
"endpoint": airbrake.Endpoint,
}).Info("Failed to send error to Airbrake")
}
return nil
}
// `Levels()` returns a slice of `Levels` the hook is fired for.
func (hook *AirbrakeHook) Levels() []log.Level {
return []log.Level{
log.ErrorLevel,
log.FatalLevel,
log.PanicLevel,
}
}
```
Logrus comes with built-in hooks. Add those, or your custom hook, in `init`:
```go
import (
log "github.com/Sirupsen/logrus"
"github.com/Sirupsen/logrus/hooks/airbrake"
"github.com/Sirupsen/logrus/hooks/syslog"
)
func init() {
log.AddHook(new(logrus_airbrake.AirbrakeHook))
log.AddHook(logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, ""))
}
```
* [`github.com/Sirupsen/logrus/hooks/airbrake`](https://github.com/Sirupsen/logrus/blob/master/hooks/airbrake/airbrake.go)
Send errors to an exception tracking service compatible with the Airbrake API.
Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes.
* [`github.com/Sirupsen/logrus/hooks/papertrail`](https://github.com/Sirupsen/logrus/blob/master/hooks/papertrail/papertrail.go)
Send errors to the Papertrail hosted logging service via UDP.
* [`github.com/Sirupsen/logrus/hooks/syslog`](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go)
Send errors to remote syslog server.
Uses standard library `log/syslog` behind the scenes.
* [`github.com/nubo/hiprus`](https://github.com/nubo/hiprus)
Send errors to a channel in hipchat.
#### Level logging
Logrus has six logging levels: Debug, Info, Warning, Error, Fatal and Panic.
```go
log.Debug("Useful debugging information.")
log.Info("Something noteworthy happened!")
log.Warn("You should probably take a look at this.")
log.Error("Something failed but I'm not quitting.")
// Calls os.Exit(1) after logging
log.Fatal("Bye.")
// Calls panic() after logging
log.Panic("I'm bailing.")
```
You can set the logging level on a `Logger`, then it will only log entries with
that severity or anything above it:
```go
// Will log anything that is info or above (warn, error, fatal, panic). Default.
log.SetLevel(log.InfoLevel)
```
It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose
environment if your application has that.
#### Entries
Besides the fields added with `WithField` or `WithFields` some fields are
automatically added to all logging events:
1. `time`. The timestamp when the entry was created.
2. `msg`. The logging message passed to `{Info,Warn,Error,Fatal,Panic}` after
the `AddFields` call. E.g. `Failed to send event.`
3. `level`. The logging level. E.g. `info`.
#### Environments
Logrus has no notion of environment.
If you wish for hooks and formatters to only be used in specific environments,
you should handle that yourself. For example, if your application has a global
variable `Environment`, which is a string representation of the environment you
could do:
```go
import (
log "github.com/Sirupsen/logrus"
)
init() {
// do something here to set environment depending on an environment variable
// or command-line flag
if Environment == "production" {
log.SetFormatter(logrus.JSONFormatter)
} else {
// The TextFormatter is default, you don't actually have to do this.
log.SetFormatter(logrus.TextFormatter)
}
}
```
This configuration is how `logrus` was intended to be used, but JSON in
production is mostly only useful if you do log aggregation with tools like
Splunk or Logstash.
#### Formatters
The built-in logging formatters are:
* `logrus.TextFormatter`. Logs the event in colors if stdout is a tty, otherwise
without colors.
* *Note:* to force colored output when there is no TTY, set the `ForceColors`
field to `true`. To force no colored output even if there is a TTY set the
`DisableColors` field to `true`
* `logrus.JSONFormatter`. Logs fields as JSON.
Third party logging formatters:
* [`zalgo`](https://github.com/aybabtme/logzalgo): invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
You can define your formatter by implementing the `Formatter` interface,
requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
`Fields` type (`map[string]interface{}`) with all your fields as well as the
default ones (see Entries section above):
```go
type MyJSONFormatter struct {
}
log.SetFormatter(new(MyJSONFormatter))
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
// Note this doesn't include Time, Level and Message which are available on
// the Entry. Consult `godoc` on information about those fields or read the
// source of the official loggers.
serialized, err := json.Marshal(entry.Data)
if err != nil {
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
}
return append(serialized, '\n'), nil
}
```
#### Rotation
Log rotation is not provided with Logrus. Log rotation should be done by an
external program (like `logrotated(8)`) that can compress and delete old log
entries. It should not be a feature of the application-level logger.
[godoc]: https://godoc.org/github.com/Sirupsen/logrus

View File

@ -0,0 +1,248 @@
package logrus
import (
"bytes"
"fmt"
"io"
"os"
"time"
)
// An entry is the final or intermediate Logrus logging entry. It contains all
// the fields passed with WithField{,s}. It's finally logged when Debug, Info,
// Warn, Error, Fatal or Panic is called on it. These objects can be reused and
// passed around as much as you wish to avoid field duplication.
type Entry struct {
Logger *Logger
// Contains all the fields set by the user.
Data Fields
// Time at which the log entry was created
Time time.Time
// Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic
Level Level
// Message passed to Debug, Info, Warn, Error, Fatal or Panic
Message string
}
func NewEntry(logger *Logger) *Entry {
return &Entry{
Logger: logger,
// Default is three fields, give a little extra room
Data: make(Fields, 5),
}
}
// Returns a reader for the entry, which is a proxy to the formatter.
func (entry *Entry) Reader() (*bytes.Buffer, error) {
serialized, err := entry.Logger.Formatter.Format(entry)
return bytes.NewBuffer(serialized), err
}
// Returns the string representation from the reader and ultimately the
// formatter.
func (entry *Entry) String() (string, error) {
reader, err := entry.Reader()
if err != nil {
return "", err
}
return reader.String(), err
}
// Add a single field to the Entry.
func (entry *Entry) WithField(key string, value interface{}) *Entry {
return entry.WithFields(Fields{key: value})
}
// Add a map of fields to the Entry.
func (entry *Entry) WithFields(fields Fields) *Entry {
data := Fields{}
for k, v := range entry.Data {
data[k] = v
}
for k, v := range fields {
data[k] = v
}
return &Entry{Logger: entry.Logger, Data: data}
}
func (entry *Entry) log(level Level, msg string) {
entry.Time = time.Now()
entry.Level = level
entry.Message = msg
if err := entry.Logger.Hooks.Fire(level, entry); err != nil {
entry.Logger.mu.Lock()
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
entry.Logger.mu.Unlock()
}
reader, err := entry.Reader()
if err != nil {
entry.Logger.mu.Lock()
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
entry.Logger.mu.Unlock()
}
entry.Logger.mu.Lock()
defer entry.Logger.mu.Unlock()
_, err = io.Copy(entry.Logger.Out, reader)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
}
// To avoid Entry#log() returning a value that only would make sense for
// panic() to use in Entry#Panic(), we avoid the allocation by checking
// directly here.
if level <= PanicLevel {
panic(reader.String())
}
}
func (entry *Entry) Debug(args ...interface{}) {
if entry.Logger.Level >= DebugLevel {
entry.log(DebugLevel, fmt.Sprint(args...))
}
}
func (entry *Entry) Print(args ...interface{}) {
entry.Info(args...)
}
func (entry *Entry) Info(args ...interface{}) {
if entry.Logger.Level >= InfoLevel {
entry.log(InfoLevel, fmt.Sprint(args...))
}
}
func (entry *Entry) Warn(args ...interface{}) {
if entry.Logger.Level >= WarnLevel {
entry.log(WarnLevel, fmt.Sprint(args...))
}
}
func (entry *Entry) Error(args ...interface{}) {
if entry.Logger.Level >= ErrorLevel {
entry.log(ErrorLevel, fmt.Sprint(args...))
}
}
func (entry *Entry) Fatal(args ...interface{}) {
if entry.Logger.Level >= FatalLevel {
entry.log(FatalLevel, fmt.Sprint(args...))
}
os.Exit(1)
}
func (entry *Entry) Panic(args ...interface{}) {
if entry.Logger.Level >= PanicLevel {
entry.log(PanicLevel, fmt.Sprint(args...))
}
panic(fmt.Sprint(args...))
}
// Entry Printf family functions
func (entry *Entry) Debugf(format string, args ...interface{}) {
if entry.Logger.Level >= DebugLevel {
entry.Debug(fmt.Sprintf(format, args...))
}
}
func (entry *Entry) Infof(format string, args ...interface{}) {
if entry.Logger.Level >= InfoLevel {
entry.Info(fmt.Sprintf(format, args...))
}
}
func (entry *Entry) Printf(format string, args ...interface{}) {
entry.Infof(format, args...)
}
func (entry *Entry) Warnf(format string, args ...interface{}) {
if entry.Logger.Level >= WarnLevel {
entry.Warn(fmt.Sprintf(format, args...))
}
}
func (entry *Entry) Warningf(format string, args ...interface{}) {
entry.Warnf(format, args...)
}
func (entry *Entry) Errorf(format string, args ...interface{}) {
if entry.Logger.Level >= ErrorLevel {
entry.Error(fmt.Sprintf(format, args...))
}
}
func (entry *Entry) Fatalf(format string, args ...interface{}) {
if entry.Logger.Level >= FatalLevel {
entry.Fatal(fmt.Sprintf(format, args...))
}
}
func (entry *Entry) Panicf(format string, args ...interface{}) {
if entry.Logger.Level >= PanicLevel {
entry.Panic(fmt.Sprintf(format, args...))
}
}
// Entry Println family functions
func (entry *Entry) Debugln(args ...interface{}) {
if entry.Logger.Level >= DebugLevel {
entry.Debug(entry.sprintlnn(args...))
}
}
func (entry *Entry) Infoln(args ...interface{}) {
if entry.Logger.Level >= InfoLevel {
entry.Info(entry.sprintlnn(args...))
}
}
func (entry *Entry) Println(args ...interface{}) {
entry.Infoln(args...)
}
func (entry *Entry) Warnln(args ...interface{}) {
if entry.Logger.Level >= WarnLevel {
entry.Warn(entry.sprintlnn(args...))
}
}
func (entry *Entry) Warningln(args ...interface{}) {
entry.Warnln(args...)
}
func (entry *Entry) Errorln(args ...interface{}) {
if entry.Logger.Level >= ErrorLevel {
entry.Error(entry.sprintlnn(args...))
}
}
func (entry *Entry) Fatalln(args ...interface{}) {
if entry.Logger.Level >= FatalLevel {
entry.Fatal(entry.sprintlnn(args...))
}
}
func (entry *Entry) Panicln(args ...interface{}) {
if entry.Logger.Level >= PanicLevel {
entry.Panic(entry.sprintlnn(args...))
}
}
// Sprintlnn => Sprint no newline. This is to get the behavior of how
// fmt.Sprintln where spaces are always added between operands, regardless of
// their type. Instead of vendoring the Sprintln implementation to spare a
// string allocation, we do the simplest thing.
func (entry *Entry) sprintlnn(args ...interface{}) string {
msg := fmt.Sprintln(args...)
return msg[:len(msg)-1]
}

View File

@ -0,0 +1,29 @@
package main
import (
"github.com/Sirupsen/logrus"
)
var log = logrus.New()
func init() {
log.Formatter = new(logrus.JSONFormatter)
log.Formatter = new(logrus.TextFormatter) // default
}
func main() {
log.WithFields(logrus.Fields{
"animal": "walrus",
"size": 10,
}).Info("A group of walrus emerges from the ocean")
log.WithFields(logrus.Fields{
"omg": true,
"number": 122,
}).Warn("The group's number increased tremendously!")
log.WithFields(logrus.Fields{
"omg": true,
"number": 100,
}).Fatal("The ice breaks!")
}

View File

@ -0,0 +1,35 @@
package main
import (
"github.com/Sirupsen/logrus"
"github.com/Sirupsen/logrus/hooks/airbrake"
"github.com/tobi/airbrake-go"
)
var log = logrus.New()
func init() {
log.Formatter = new(logrus.TextFormatter) // default
log.Hooks.Add(new(logrus_airbrake.AirbrakeHook))
}
func main() {
airbrake.Endpoint = "https://exceptions.whatever.com/notifier_api/v2/notices.xml"
airbrake.ApiKey = "whatever"
airbrake.Environment = "production"
log.WithFields(logrus.Fields{
"animal": "walrus",
"size": 10,
}).Info("A group of walrus emerges from the ocean")
log.WithFields(logrus.Fields{
"omg": true,
"number": 122,
}).Warn("The group's number increased tremendously!")
log.WithFields(logrus.Fields{
"omg": true,
"number": 100,
}).Fatal("The ice breaks!")
}

View File

@ -0,0 +1,177 @@
package logrus
import (
"io"
)
var (
// std is the name of the standard logger in stdlib `log`
std = New()
)
// SetOutput sets the standard logger output.
func SetOutput(out io.Writer) {
std.mu.Lock()
defer std.mu.Unlock()
std.Out = out
}
// SetFormatter sets the standard logger formatter.
func SetFormatter(formatter Formatter) {
std.mu.Lock()
defer std.mu.Unlock()
std.Formatter = formatter
}
// SetLevel sets the standard logger level.
func SetLevel(level Level) {
std.mu.Lock()
defer std.mu.Unlock()
std.Level = level
}
// AddHook adds a hook to the standard logger hooks.
func AddHook(hook Hook) {
std.mu.Lock()
defer std.mu.Unlock()
std.Hooks.Add(hook)
}
// WithField creates an entry from the standard logger and adds a field to
// it. If you want multiple fields, use `WithFields`.
//
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
// or Panic on the Entry it returns.
func WithField(key string, value interface{}) *Entry {
return std.WithField(key, value)
}
// WithFields creates an entry from the standard logger and adds multiple
// fields to it. This is simply a helper for `WithField`, invoking it
// once for each field.
//
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
// or Panic on the Entry it returns.
func WithFields(fields Fields) *Entry {
return std.WithFields(fields)
}
// Debug logs a message at level Debug on the standard logger.
func Debug(args ...interface{}) {
std.Debug(args...)
}
// Print logs a message at level Info on the standard logger.
func Print(args ...interface{}) {
std.Print(args...)
}
// Info logs a message at level Info on the standard logger.
func Info(args ...interface{}) {
std.Info(args...)
}
// Warn logs a message at level Warn on the standard logger.
func Warn(args ...interface{}) {
std.Warn(args...)
}
// Warning logs a message at level Warn on the standard logger.
func Warning(args ...interface{}) {
std.Warning(args...)
}
// Error logs a message at level Error on the standard logger.
func Error(args ...interface{}) {
std.Error(args...)
}
// Panic logs a message at level Panic on the standard logger.
func Panic(args ...interface{}) {
std.Panic(args...)
}
// Fatal logs a message at level Fatal on the standard logger.
func Fatal(args ...interface{}) {
std.Fatal(args...)
}
// Debugf logs a message at level Debug on the standard logger.
func Debugf(format string, args ...interface{}) {
std.Debugf(format, args...)
}
// Printf logs a message at level Info on the standard logger.
func Printf(format string, args ...interface{}) {
std.Printf(format, args...)
}
// Infof logs a message at level Info on the standard logger.
func Infof(format string, args ...interface{}) {
std.Infof(format, args...)
}
// Warnf logs a message at level Warn on the standard logger.
func Warnf(format string, args ...interface{}) {
std.Warnf(format, args...)
}
// Warningf logs a message at level Warn on the standard logger.
func Warningf(format string, args ...interface{}) {
std.Warningf(format, args...)
}
// Errorf logs a message at level Error on the standard logger.
func Errorf(format string, args ...interface{}) {
std.Errorf(format, args...)
}
// Panicf logs a message at level Panic on the standard logger.
func Panicf(format string, args ...interface{}) {
std.Panicf(format, args...)
}
// Fatalf logs a message at level Fatal on the standard logger.
func Fatalf(format string, args ...interface{}) {
std.Fatalf(format, args...)
}
// Debugln logs a message at level Debug on the standard logger.
func Debugln(args ...interface{}) {
std.Debugln(args...)
}
// Println logs a message at level Info on the standard logger.
func Println(args ...interface{}) {
std.Println(args...)
}
// Infoln logs a message at level Info on the standard logger.
func Infoln(args ...interface{}) {
std.Infoln(args...)
}
// Warnln logs a message at level Warn on the standard logger.
func Warnln(args ...interface{}) {
std.Warnln(args...)
}
// Warningln logs a message at level Warn on the standard logger.
func Warningln(args ...interface{}) {
std.Warningln(args...)
}
// Errorln logs a message at level Error on the standard logger.
func Errorln(args ...interface{}) {
std.Errorln(args...)
}
// Panicln logs a message at level Panic on the standard logger.
func Panicln(args ...interface{}) {
std.Panicln(args...)
}
// Fatalln logs a message at level Fatal on the standard logger.
func Fatalln(args ...interface{}) {
std.Fatalln(args...)
}

View File

@ -0,0 +1,44 @@
package logrus
// The Formatter interface is used to implement a custom Formatter. It takes an
// `Entry`. It exposes all the fields, including the default ones:
//
// * `entry.Data["msg"]`. The message passed from Info, Warn, Error ..
// * `entry.Data["time"]`. The timestamp.
// * `entry.Data["level"]. The level the entry was logged at.
//
// Any additional fields added with `WithField` or `WithFields` are also in
// `entry.Data`. Format is expected to return an array of bytes which are then
// logged to `logger.Out`.
type Formatter interface {
Format(*Entry) ([]byte, error)
}
// This is to not silently overwrite `time`, `msg` and `level` fields when
// dumping it. If this code wasn't there doing:
//
// logrus.WithField("level", 1).Info("hello")
//
// Would just silently drop the user provided level. Instead with this code
// it'll logged as:
//
// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}
//
// It's not exported because it's still using Data in an opinionated way. It's to
// avoid code duplication between the two default formatters.
func prefixFieldClashes(entry *Entry) {
_, ok := entry.Data["time"]
if ok {
entry.Data["fields.time"] = entry.Data["time"]
}
_, ok = entry.Data["msg"]
if ok {
entry.Data["fields.msg"] = entry.Data["msg"]
}
_, ok = entry.Data["level"]
if ok {
entry.Data["fields.level"] = entry.Data["level"]
}
}

View File

@ -0,0 +1,88 @@
package logrus
import (
"testing"
"time"
)
// smallFields is a small size data set for benchmarking
var smallFields = Fields{
"foo": "bar",
"baz": "qux",
"one": "two",
"three": "four",
}
// largeFields is a large size data set for benchmarking
var largeFields = Fields{
"foo": "bar",
"baz": "qux",
"one": "two",
"three": "four",
"five": "six",
"seven": "eight",
"nine": "ten",
"eleven": "twelve",
"thirteen": "fourteen",
"fifteen": "sixteen",
"seventeen": "eighteen",
"nineteen": "twenty",
"a": "b",
"c": "d",
"e": "f",
"g": "h",
"i": "j",
"k": "l",
"m": "n",
"o": "p",
"q": "r",
"s": "t",
"u": "v",
"w": "x",
"y": "z",
"this": "will",
"make": "thirty",
"entries": "yeah",
}
func BenchmarkSmallTextFormatter(b *testing.B) {
doBenchmark(b, &TextFormatter{DisableColors: true}, smallFields)
}
func BenchmarkLargeTextFormatter(b *testing.B) {
doBenchmark(b, &TextFormatter{DisableColors: true}, largeFields)
}
func BenchmarkSmallColoredTextFormatter(b *testing.B) {
doBenchmark(b, &TextFormatter{ForceColors: true}, smallFields)
}
func BenchmarkLargeColoredTextFormatter(b *testing.B) {
doBenchmark(b, &TextFormatter{ForceColors: true}, largeFields)
}
func BenchmarkSmallJSONFormatter(b *testing.B) {
doBenchmark(b, &JSONFormatter{}, smallFields)
}
func BenchmarkLargeJSONFormatter(b *testing.B) {
doBenchmark(b, &JSONFormatter{}, largeFields)
}
func doBenchmark(b *testing.B, formatter Formatter, fields Fields) {
entry := &Entry{
Time: time.Time{},
Level: InfoLevel,
Message: "message",
Data: fields,
}
var d []byte
var err error
for i := 0; i < b.N; i++ {
d, err = formatter.Format(entry)
if err != nil {
b.Fatal(err)
}
b.SetBytes(int64(len(d)))
}
}

View File

@ -0,0 +1,122 @@
package logrus
import (
"testing"
"github.com/stretchr/testify/assert"
)
type TestHook struct {
Fired bool
}
func (hook *TestHook) Fire(entry *Entry) error {
hook.Fired = true
return nil
}
func (hook *TestHook) Levels() []Level {
return []Level{
DebugLevel,
InfoLevel,
WarnLevel,
ErrorLevel,
FatalLevel,
PanicLevel,
}
}
func TestHookFires(t *testing.T) {
hook := new(TestHook)
LogAndAssertJSON(t, func(log *Logger) {
log.Hooks.Add(hook)
assert.Equal(t, hook.Fired, false)
log.Print("test")
}, func(fields Fields) {
assert.Equal(t, hook.Fired, true)
})
}
type ModifyHook struct {
}
func (hook *ModifyHook) Fire(entry *Entry) error {
entry.Data["wow"] = "whale"
return nil
}
func (hook *ModifyHook) Levels() []Level {
return []Level{
DebugLevel,
InfoLevel,
WarnLevel,
ErrorLevel,
FatalLevel,
PanicLevel,
}
}
func TestHookCanModifyEntry(t *testing.T) {
hook := new(ModifyHook)
LogAndAssertJSON(t, func(log *Logger) {
log.Hooks.Add(hook)
log.WithField("wow", "elephant").Print("test")
}, func(fields Fields) {
assert.Equal(t, fields["wow"], "whale")
})
}
func TestCanFireMultipleHooks(t *testing.T) {
hook1 := new(ModifyHook)
hook2 := new(TestHook)
LogAndAssertJSON(t, func(log *Logger) {
log.Hooks.Add(hook1)
log.Hooks.Add(hook2)
log.WithField("wow", "elephant").Print("test")
}, func(fields Fields) {
assert.Equal(t, fields["wow"], "whale")
assert.Equal(t, hook2.Fired, true)
})
}
type ErrorHook struct {
Fired bool
}
func (hook *ErrorHook) Fire(entry *Entry) error {
hook.Fired = true
return nil
}
func (hook *ErrorHook) Levels() []Level {
return []Level{
ErrorLevel,
}
}
func TestErrorHookShouldntFireOnInfo(t *testing.T) {
hook := new(ErrorHook)
LogAndAssertJSON(t, func(log *Logger) {
log.Hooks.Add(hook)
log.Info("test")
}, func(fields Fields) {
assert.Equal(t, hook.Fired, false)
})
}
func TestErrorHookShouldFireOnError(t *testing.T) {
hook := new(ErrorHook)
LogAndAssertJSON(t, func(log *Logger) {
log.Hooks.Add(hook)
log.Error("test")
}, func(fields Fields) {
assert.Equal(t, hook.Fired, true)
})
}

View File

@ -0,0 +1,34 @@
package logrus
// A hook to be fired when logging on the logging levels returned from
// `Levels()` on your implementation of the interface. Note that this is not
// fired in a goroutine or a channel with workers, you should handle such
// functionality yourself if your call is non-blocking and you don't wish for
// the logging calls for levels returned from `Levels()` to block.
type Hook interface {
Levels() []Level
Fire(*Entry) error
}
// Internal type for storing the hooks on a logger instance.
type levelHooks map[Level][]Hook
// Add a hook to an instance of logger. This is called with
// `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface.
func (hooks levelHooks) Add(hook Hook) {
for _, level := range hook.Levels() {
hooks[level] = append(hooks[level], hook)
}
}
// Fire all the hooks for the passed level. Used by `entry.log` to fire
// appropriate hooks for a log entry.
func (hooks levelHooks) Fire(level Level, entry *Entry) error {
for _, hook := range hooks[level] {
if err := hook.Fire(entry); err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,54 @@
package logrus_airbrake
import (
"github.com/Sirupsen/logrus"
"github.com/tobi/airbrake-go"
)
// AirbrakeHook to send exceptions to an exception-tracking service compatible
// with the Airbrake API. You must set:
// * airbrake.Endpoint
// * airbrake.ApiKey
// * airbrake.Environment (only sends exceptions when set to "production")
//
// Before using this hook, to send an error. Entries that trigger an Error,
// Fatal or Panic should now include an "error" field to send to Airbrake.
type AirbrakeHook struct{}
func (hook *AirbrakeHook) Fire(entry *logrus.Entry) error {
if entry.Data["error"] == nil {
entry.Logger.WithFields(logrus.Fields{
"source": "airbrake",
"endpoint": airbrake.Endpoint,
}).Warn("Exceptions sent to Airbrake must have an 'error' key with the error")
return nil
}
err, ok := entry.Data["error"].(error)
if !ok {
entry.Logger.WithFields(logrus.Fields{
"source": "airbrake",
"endpoint": airbrake.Endpoint,
}).Warn("Exceptions sent to Airbrake must have an `error` key of type `error`")
return nil
}
airErr := airbrake.Notify(err)
if airErr != nil {
entry.Logger.WithFields(logrus.Fields{
"source": "airbrake",
"endpoint": airbrake.Endpoint,
"error": airErr,
}).Warn("Failed to send error to Airbrake")
}
return nil
}
func (hook *AirbrakeHook) Levels() []logrus.Level {
return []logrus.Level{
logrus.ErrorLevel,
logrus.FatalLevel,
logrus.PanicLevel,
}
}

View File

@ -0,0 +1,28 @@
# Papertrail Hook for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:" />
[Papertrail](https://papertrailapp.com) provides hosted log management. Once stored in Papertrail, you can [group](http://help.papertrailapp.com/kb/how-it-works/groups/) your logs on various dimensions, [search](http://help.papertrailapp.com/kb/how-it-works/search-syntax) them, and trigger [alerts](http://help.papertrailapp.com/kb/how-it-works/alerts).
In most deployments, you'll want to send logs to Papertrail via their [remote_syslog](http://help.papertrailapp.com/kb/configuration/configuring-centralized-logging-from-text-log-files-in-unix/) daemon, which requires no application-specific configuration. This hook is intended for relatively low-volume logging, likely in managed cloud hosting deployments where installing `remote_syslog` is not possible.
## Usage
You can find your Papertrail UDP port on your [Papertrail account page](https://papertrailapp.com/account/destinations). Substitute it below for `YOUR_PAPERTRAIL_UDP_PORT`.
For `YOUR_APP_NAME`, substitute a short string that will readily identify your application or service in the logs.
```go
import (
"log/syslog"
"github.com/Sirupsen/logrus"
"github.com/Sirupsen/logrus/hooks/papertrail"
)
func main() {
log := logrus.New()
hook, err := logrus_papertrail.NewPapertrailHook("logs.papertrailapp.com", YOUR_PAPERTRAIL_UDP_PORT, YOUR_APP_NAME)
if err == nil {
log.Hooks.Add(hook)
}
}
```

View File

@ -0,0 +1,54 @@
package logrus_papertrail
import (
"fmt"
"net"
"os"
"time"
"github.com/Sirupsen/logrus"
)
const (
format = "Jan 2 15:04:05"
)
// PapertrailHook to send logs to a logging service compatible with the Papertrail API.
type PapertrailHook struct {
Host string
Port int
AppName string
UDPConn net.Conn
}
// NewPapertrailHook creates a hook to be added to an instance of logger.
func NewPapertrailHook(host string, port int, appName string) (*PapertrailHook, error) {
conn, err := net.Dial("udp", fmt.Sprintf("%s:%d", host, port))
return &PapertrailHook{host, port, appName, conn}, err
}
// Fire is called when a log event is fired.
func (hook *PapertrailHook) Fire(entry *logrus.Entry) error {
date := time.Now().Format(format)
payload := fmt.Sprintf("<22> %s %s: [%s] %s", date, hook.AppName, entry.Data["level"], entry.Message)
bytesWritten, err := hook.UDPConn.Write([]byte(payload))
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to send log line to Papertrail via UDP. Wrote %d bytes before error: %v", bytesWritten, err)
return err
}
return nil
}
// Levels returns the available logging levels.
func (hook *PapertrailHook) Levels() []logrus.Level {
return []logrus.Level{
logrus.PanicLevel,
logrus.FatalLevel,
logrus.ErrorLevel,
logrus.WarnLevel,
logrus.InfoLevel,
logrus.DebugLevel,
}
}

View File

@ -0,0 +1,26 @@
package logrus_papertrail
import (
"fmt"
"testing"
"github.com/Sirupsen/logrus"
"github.com/stvp/go-udp-testing"
)
func TestWritingToUDP(t *testing.T) {
port := 16661
udp.SetAddr(fmt.Sprintf(":%d", port))
hook, err := NewPapertrailHook("localhost", port, "test")
if err != nil {
t.Errorf("Unable to connect to local UDP server.")
}
log := logrus.New()
log.Hooks.Add(hook)
udp.ShouldReceive(t, "foo", func() {
log.Info("foo")
})
}

View File

@ -0,0 +1,20 @@
# Syslog Hooks for Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/>
## Usage
```go
import (
"log/syslog"
"github.com/Sirupsen/logrus"
"github.com/Sirupsen/logrus/hooks/syslog"
)
func main() {
log := logrus.New()
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
if err == nil {
log.Hooks.Add(hook)
}
}
```

View File

@ -0,0 +1,59 @@
package logrus_syslog
import (
"fmt"
"github.com/Sirupsen/logrus"
"log/syslog"
"os"
)
// SyslogHook to send logs via syslog.
type SyslogHook struct {
Writer *syslog.Writer
SyslogNetwork string
SyslogRaddr string
}
// Creates a hook to be added to an instance of logger. This is called with
// `hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_DEBUG, "")`
// `if err == nil { log.Hooks.Add(hook) }`
func NewSyslogHook(network, raddr string, priority syslog.Priority, tag string) (*SyslogHook, error) {
w, err := syslog.Dial(network, raddr, priority, tag)
return &SyslogHook{w, network, raddr}, err
}
func (hook *SyslogHook) Fire(entry *logrus.Entry) error {
line, err := entry.String()
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to read entry, %v", err)
return err
}
switch entry.Data["level"] {
case "panic":
return hook.Writer.Crit(line)
case "fatal":
return hook.Writer.Crit(line)
case "error":
return hook.Writer.Err(line)
case "warn":
return hook.Writer.Warning(line)
case "info":
return hook.Writer.Info(line)
case "debug":
return hook.Writer.Debug(line)
default:
return nil
}
}
func (hook *SyslogHook) Levels() []logrus.Level {
return []logrus.Level{
logrus.PanicLevel,
logrus.FatalLevel,
logrus.ErrorLevel,
logrus.WarnLevel,
logrus.InfoLevel,
logrus.DebugLevel,
}
}

View File

@ -0,0 +1,26 @@
package logrus_syslog
import (
"github.com/Sirupsen/logrus"
"log/syslog"
"testing"
)
func TestLocalhostAddAndPrint(t *testing.T) {
log := logrus.New()
hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
if err != nil {
t.Errorf("Unable to connect to local syslog.")
}
log.Hooks.Add(hook)
for _, level := range hook.Levels() {
if len(log.Hooks[level]) != 1 {
t.Errorf("SyslogHook was not added. The length of log.Hooks[%v]: %v", level, len(log.Hooks[level]))
}
}
log.Info("Congratulations!")
}

View File

@ -0,0 +1,22 @@
package logrus
import (
"encoding/json"
"fmt"
"time"
)
type JSONFormatter struct{}
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
prefixFieldClashes(entry)
entry.Data["time"] = entry.Time.Format(time.RFC3339)
entry.Data["msg"] = entry.Message
entry.Data["level"] = entry.Level.String()
serialized, err := json.Marshal(entry.Data)
if err != nil {
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
}
return append(serialized, '\n'), nil
}

View File

@ -0,0 +1,161 @@
package logrus
import (
"io"
"os"
"sync"
)
type Logger struct {
// The logs are `io.Copy`'d to this in a mutex. It's common to set this to a
// file, or leave it default which is `os.Stdout`. You can also set this to
// something more adventorous, such as logging to Kafka.
Out io.Writer
// Hooks for the logger instance. These allow firing events based on logging
// levels and log entries. For example, to send errors to an error tracking
// service, log to StatsD or dump the core on fatal errors.
Hooks levelHooks
// All log entries pass through the formatter before logged to Out. The
// included formatters are `TextFormatter` and `JSONFormatter` for which
// TextFormatter is the default. In development (when a TTY is attached) it
// logs with colors, but to a file it wouldn't. You can easily implement your
// own that implements the `Formatter` interface, see the `README` or included
// formatters for examples.
Formatter Formatter
// The logging level the logger should log at. This is typically (and defaults
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
// logged. `logrus.Debug` is useful in
Level Level
// Used to sync writing to the log.
mu sync.Mutex
}
// Creates a new logger. Configuration should be set by changing `Formatter`,
// `Out` and `Hooks` directly on the default logger instance. You can also just
// instantiate your own:
//
// var log = &Logger{
// Out: os.Stderr,
// Formatter: new(JSONFormatter),
// Hooks: make(levelHooks),
// Level: logrus.Debug,
// }
//
// It's recommended to make this a global instance called `log`.
func New() *Logger {
return &Logger{
Out: os.Stdout,
Formatter: new(TextFormatter),
Hooks: make(levelHooks),
Level: InfoLevel,
}
}
// Adds a field to the log entry, note that you it doesn't log until you call
// Debug, Print, Info, Warn, Fatal or Panic. It only creates a log entry.
// Ff you want multiple fields, use `WithFields`.
func (logger *Logger) WithField(key string, value interface{}) *Entry {
return NewEntry(logger).WithField(key, value)
}
// Adds a struct of fields to the log entry. All it does is call `WithField` for
// each `Field`.
func (logger *Logger) WithFields(fields Fields) *Entry {
return NewEntry(logger).WithFields(fields)
}
func (logger *Logger) Debugf(format string, args ...interface{}) {
NewEntry(logger).Debugf(format, args...)
}
func (logger *Logger) Infof(format string, args ...interface{}) {
NewEntry(logger).Infof(format, args...)
}
func (logger *Logger) Printf(format string, args ...interface{}) {
NewEntry(logger).Printf(format, args...)
}
func (logger *Logger) Warnf(format string, args ...interface{}) {
NewEntry(logger).Warnf(format, args...)
}
func (logger *Logger) Warningf(format string, args ...interface{}) {
NewEntry(logger).Warnf(format, args...)
}
func (logger *Logger) Errorf(format string, args ...interface{}) {
NewEntry(logger).Errorf(format, args...)
}
func (logger *Logger) Fatalf(format string, args ...interface{}) {
NewEntry(logger).Fatalf(format, args...)
}
func (logger *Logger) Panicf(format string, args ...interface{}) {
NewEntry(logger).Panicf(format, args...)
}
func (logger *Logger) Debug(args ...interface{}) {
NewEntry(logger).Debug(args...)
}
func (logger *Logger) Info(args ...interface{}) {
NewEntry(logger).Info(args...)
}
func (logger *Logger) Print(args ...interface{}) {
NewEntry(logger).Info(args...)
}
func (logger *Logger) Warn(args ...interface{}) {
NewEntry(logger).Warn(args...)
}
func (logger *Logger) Warning(args ...interface{}) {
NewEntry(logger).Warn(args...)
}
func (logger *Logger) Error(args ...interface{}) {
NewEntry(logger).Error(args...)
}
func (logger *Logger) Fatal(args ...interface{}) {
NewEntry(logger).Fatal(args...)
}
func (logger *Logger) Panic(args ...interface{}) {
NewEntry(logger).Panic(args...)
}
func (logger *Logger) Debugln(args ...interface{}) {
NewEntry(logger).Debugln(args...)
}
func (logger *Logger) Infoln(args ...interface{}) {
NewEntry(logger).Infoln(args...)
}
func (logger *Logger) Println(args ...interface{}) {
NewEntry(logger).Println(args...)
}
func (logger *Logger) Warnln(args ...interface{}) {
NewEntry(logger).Warnln(args...)
}
func (logger *Logger) Warningln(args ...interface{}) {
NewEntry(logger).Warnln(args...)
}
func (logger *Logger) Errorln(args ...interface{}) {
NewEntry(logger).Errorln(args...)
}
func (logger *Logger) Fatalln(args ...interface{}) {
NewEntry(logger).Fatalln(args...)
}
func (logger *Logger) Panicln(args ...interface{}) {
NewEntry(logger).Panicln(args...)
}

View File

@ -0,0 +1,94 @@
package logrus
import (
"fmt"
"log"
)
// Fields type, used to pass to `WithFields`.
type Fields map[string]interface{}
// Level type
type Level uint8
// Convert the Level to a string. E.g. PanicLevel becomes "panic".
func (level Level) String() string {
switch level {
case DebugLevel:
return "debug"
case InfoLevel:
return "info"
case WarnLevel:
return "warning"
case ErrorLevel:
return "error"
case FatalLevel:
return "fatal"
case PanicLevel:
return "panic"
}
return "unknown"
}
// ParseLevel takes a string level and returns the Logrus log level constant.
func ParseLevel(lvl string) (Level, error) {
switch lvl {
case "panic":
return PanicLevel, nil
case "fatal":
return FatalLevel, nil
case "error":
return ErrorLevel, nil
case "warn", "warning":
return WarnLevel, nil
case "info":
return InfoLevel, nil
case "debug":
return DebugLevel, nil
}
var l Level
return l, fmt.Errorf("not a valid logrus Level: %q", lvl)
}
// These are the different logging levels. You can set the logging level to log
// on your instance of logger, obtained with `logrus.New()`.
const (
// PanicLevel level, highest level of severity. Logs and then calls panic with the
// message passed to Debug, Info, ...
PanicLevel Level = iota
// FatalLevel level. Logs and then calls `os.Exit(1)`. It will exit even if the
// logging level is set to Panic.
FatalLevel
// ErrorLevel level. Logs. Used for errors that should definitely be noted.
// Commonly used for hooks to send errors to an error tracking service.
ErrorLevel
// WarnLevel level. Non-critical entries that deserve eyes.
WarnLevel
// InfoLevel level. General operational entries about what's going on inside the
// application.
InfoLevel
// DebugLevel level. Usually only enabled when debugging. Very verbose logging.
DebugLevel
)
// Won't compile if StdLogger can't be realized by a log.Logger
var _ StdLogger = &log.Logger{}
// StdLogger is what your logrus-enabled library should take, that way
// it'll accept a stdlib logger and a logrus logger. There's no standard
// interface, this is the closest we get, unfortunately.
type StdLogger interface {
Print(...interface{})
Printf(string, ...interface{})
Println(...interface{})
Fatal(...interface{})
Fatalf(string, ...interface{})
Fatalln(...interface{})
Panic(...interface{})
Panicf(string, ...interface{})
Panicln(...interface{})
}

View File

@ -0,0 +1,247 @@
package logrus
import (
"bytes"
"encoding/json"
"strconv"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func LogAndAssertJSON(t *testing.T, log func(*Logger), assertions func(fields Fields)) {
var buffer bytes.Buffer
var fields Fields
logger := New()
logger.Out = &buffer
logger.Formatter = new(JSONFormatter)
log(logger)
err := json.Unmarshal(buffer.Bytes(), &fields)
assert.Nil(t, err)
assertions(fields)
}
func LogAndAssertText(t *testing.T, log func(*Logger), assertions func(fields map[string]string)) {
var buffer bytes.Buffer
logger := New()
logger.Out = &buffer
logger.Formatter = &TextFormatter{
DisableColors: true,
}
log(logger)
fields := make(map[string]string)
for _, kv := range strings.Split(buffer.String(), " ") {
if !strings.Contains(kv, "=") {
continue
}
kvArr := strings.Split(kv, "=")
key := strings.TrimSpace(kvArr[0])
val, err := strconv.Unquote(kvArr[1])
assert.NoError(t, err)
fields[key] = val
}
assertions(fields)
}
func TestPrint(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Print("test")
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "test")
assert.Equal(t, fields["level"], "info")
})
}
func TestInfo(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Info("test")
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "test")
assert.Equal(t, fields["level"], "info")
})
}
func TestWarn(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Warn("test")
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "test")
assert.Equal(t, fields["level"], "warning")
})
}
func TestInfolnShouldAddSpacesBetweenStrings(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Infoln("test", "test")
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "test test")
})
}
func TestInfolnShouldAddSpacesBetweenStringAndNonstring(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Infoln("test", 10)
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "test 10")
})
}
func TestInfolnShouldAddSpacesBetweenTwoNonStrings(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Infoln(10, 10)
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "10 10")
})
}
func TestInfoShouldAddSpacesBetweenTwoNonStrings(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Infoln(10, 10)
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "10 10")
})
}
func TestInfoShouldNotAddSpacesBetweenStringAndNonstring(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Info("test", 10)
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "test10")
})
}
func TestInfoShouldNotAddSpacesBetweenStrings(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.Info("test", "test")
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "testtest")
})
}
func TestWithFieldsShouldAllowAssignments(t *testing.T) {
var buffer bytes.Buffer
var fields Fields
logger := New()
logger.Out = &buffer
logger.Formatter = new(JSONFormatter)
localLog := logger.WithFields(Fields{
"key1": "value1",
})
localLog.WithField("key2", "value2").Info("test")
err := json.Unmarshal(buffer.Bytes(), &fields)
assert.Nil(t, err)
assert.Equal(t, "value2", fields["key2"])
assert.Equal(t, "value1", fields["key1"])
buffer = bytes.Buffer{}
fields = Fields{}
localLog.Info("test")
err = json.Unmarshal(buffer.Bytes(), &fields)
assert.Nil(t, err)
_, ok := fields["key2"]
assert.Equal(t, false, ok)
assert.Equal(t, "value1", fields["key1"])
}
func TestUserSuppliedFieldDoesNotOverwriteDefaults(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.WithField("msg", "hello").Info("test")
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "test")
})
}
func TestUserSuppliedMsgFieldHasPrefix(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.WithField("msg", "hello").Info("test")
}, func(fields Fields) {
assert.Equal(t, fields["msg"], "test")
assert.Equal(t, fields["fields.msg"], "hello")
})
}
func TestUserSuppliedTimeFieldHasPrefix(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.WithField("time", "hello").Info("test")
}, func(fields Fields) {
assert.Equal(t, fields["fields.time"], "hello")
})
}
func TestUserSuppliedLevelFieldHasPrefix(t *testing.T) {
LogAndAssertJSON(t, func(log *Logger) {
log.WithField("level", 1).Info("test")
}, func(fields Fields) {
assert.Equal(t, fields["level"], "info")
assert.Equal(t, fields["fields.level"], 1)
})
}
func TestDefaultFieldsAreNotPrefixed(t *testing.T) {
LogAndAssertText(t, func(log *Logger) {
ll := log.WithField("herp", "derp")
ll.Info("hello")
ll.Info("bye")
}, func(fields map[string]string) {
for _, fieldName := range []string{"fields.level", "fields.time", "fields.msg"} {
if _, ok := fields[fieldName]; ok {
t.Fatalf("should not have prefixed %q: %v", fieldName, fields)
}
}
})
}
func TestConvertLevelToString(t *testing.T) {
assert.Equal(t, "debug", DebugLevel.String())
assert.Equal(t, "info", InfoLevel.String())
assert.Equal(t, "warning", WarnLevel.String())
assert.Equal(t, "error", ErrorLevel.String())
assert.Equal(t, "fatal", FatalLevel.String())
assert.Equal(t, "panic", PanicLevel.String())
}
func TestParseLevel(t *testing.T) {
l, err := ParseLevel("panic")
assert.Nil(t, err)
assert.Equal(t, PanicLevel, l)
l, err = ParseLevel("fatal")
assert.Nil(t, err)
assert.Equal(t, FatalLevel, l)
l, err = ParseLevel("error")
assert.Nil(t, err)
assert.Equal(t, ErrorLevel, l)
l, err = ParseLevel("warn")
assert.Nil(t, err)
assert.Equal(t, WarnLevel, l)
l, err = ParseLevel("warning")
assert.Nil(t, err)
assert.Equal(t, WarnLevel, l)
l, err = ParseLevel("info")
assert.Nil(t, err)
assert.Equal(t, InfoLevel, l)
l, err = ParseLevel("debug")
assert.Nil(t, err)
assert.Equal(t, DebugLevel, l)
l, err = ParseLevel("invalid")
assert.Equal(t, "not a valid logrus Level: \"invalid\"", err.Error())
}

View File

@ -0,0 +1,12 @@
// Based on ssh/terminal:
// 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 logrus
import "syscall"
const ioctlReadTermios = syscall.TIOCGETA
type Termios syscall.Termios

View File

@ -0,0 +1,20 @@
/*
Go 1.2 doesn't include Termios for FreeBSD. This should be added in 1.3 and this could be merged with terminal_darwin.
*/
package logrus
import (
"syscall"
)
const ioctlReadTermios = syscall.TIOCGETA
type Termios struct {
Iflag uint32
Oflag uint32
Cflag uint32
Lflag uint32
Cc [20]uint8
Ispeed uint32
Ospeed uint32
}

View File

@ -0,0 +1,12 @@
// Based on ssh/terminal:
// 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 logrus
import "syscall"
const ioctlReadTermios = syscall.TCGETS
type Termios syscall.Termios

View File

@ -0,0 +1,21 @@
// Based on ssh/terminal:
// Copyright 2011 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.
// +build linux,!appengine darwin freebsd
package logrus
import (
"syscall"
"unsafe"
)
// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal() bool {
fd := syscall.Stdout
var termios Termios
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
return err == 0
}

View File

@ -0,0 +1,27 @@
// Based on ssh/terminal:
// Copyright 2011 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.
// +build windows
package logrus
import (
"syscall"
"unsafe"
)
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
var (
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
)
// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal() bool {
fd := syscall.Stdout
var st uint32
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
return r != 0 && e == 0
}

View File

@ -0,0 +1,95 @@
package logrus
import (
"bytes"
"fmt"
"sort"
"strings"
"time"
)
const (
nocolor = 0
red = 31
green = 32
yellow = 33
blue = 34
)
var (
baseTimestamp time.Time
isTerminal bool
)
func init() {
baseTimestamp = time.Now()
isTerminal = IsTerminal()
}
func miniTS() int {
return int(time.Since(baseTimestamp) / time.Second)
}
type TextFormatter struct {
// Set to true to bypass checking for a TTY before outputting colors.
ForceColors bool
DisableColors bool
}
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
var keys []string
for k := range entry.Data {
keys = append(keys, k)
}
sort.Strings(keys)
b := &bytes.Buffer{}
prefixFieldClashes(entry)
isColored := (f.ForceColors || isTerminal) && !f.DisableColors
if isColored {
printColored(b, entry, keys)
} else {
f.appendKeyValue(b, "time", entry.Time.Format(time.RFC3339))
f.appendKeyValue(b, "level", entry.Level.String())
f.appendKeyValue(b, "msg", entry.Message)
for _, key := range keys {
f.appendKeyValue(b, key, entry.Data[key])
}
}
b.WriteByte('\n')
return b.Bytes(), nil
}
func printColored(b *bytes.Buffer, entry *Entry, keys []string) {
var levelColor int
switch entry.Level {
case WarnLevel:
levelColor = yellow
case ErrorLevel, FatalLevel, PanicLevel:
levelColor = red
default:
levelColor = blue
}
levelText := strings.ToUpper(entry.Level.String())[0:4]
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message)
for _, k := range keys {
v := entry.Data[k]
fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%v", levelColor, k, v)
}
}
func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key, value interface{}) {
switch value.(type) {
case string, error:
fmt.Fprintf(b, "%v=%q ", key, value)
default:
fmt.Fprintf(b, "%v=%v ", key, value)
}
}

View File

@ -0,0 +1,50 @@
# How to Contribute
## Getting Started
- Fork the repository on GitHub
- Read the [README](README.markdown) for build and test instructions
- Play with the project, submit bugs, submit patches!
## Contribution Flow
This is a rough outline of what a contributor's workflow looks like:
- Create a topic branch from where you want to base your work (usually master).
- Make commits of logical units.
- Make sure your commit messages are in the proper format (see below).
- Push your changes to a topic branch in your fork of the repository.
- Make sure the tests pass, and add any new tests as appropriate.
- Submit a pull request to the original repository.
Thanks for your contributions!
### Format of the Commit Message
We follow a rough convention for commit messages that is designed to answer two
questions: what changed and why. The subject line should feature the what and
the body of the commit should describe the why.
```
scripts: add the test-cluster command
this uses tmux to setup a test cluster that you can easily kill and
start for debugging.
Fixes #38
```
The format can be described more formally as follows:
```
<subsystem>: <what changed>
<BLANK LINE>
<why this change was made>
<BLANK LINE>
<footer>
```
The first line is the subject and should be no longer than 70 characters, the
second line is always blank, and other lines should be wrapped at 80 characters.
This allows the message to be easier to read on GitHub as well as in various
git tools.

View File

@ -0,0 +1,2 @@
Brandon Philips <brandon@ifup.org> (@philips)
Brian Waldon <brian@waldon.cc> (@bcwaldon)

View File

@ -175,6 +175,13 @@ func (conn *Conn) BusObject() *Object {
// not be called on shared connections.
func (conn *Conn) Close() error {
conn.outLck.Lock()
if conn.closed {
// inWorker calls Close on read error, the read error may
// be caused by another caller calling Close to shutdown the
// dbus connection, a double-close scenario we prevent here.
conn.outLck.Unlock()
return nil
}
close(conn.out)
conn.closed = true
conn.outLck.Unlock()
@ -317,11 +324,7 @@ func (conn *Conn) inWorker() {
}
conn.signalsLck.Lock()
for _, ch := range conn.signals {
// don't block trying to send a signal
select {
case ch <- signal:
default:
}
ch <- signal
}
conn.signalsLck.Unlock()
case TypeMethodCall:
@ -508,6 +511,10 @@ type Error struct {
Body []interface{}
}
func NewError(name string, body []interface{}) *Error {
return &Error{name, body}
}
func (e Error) Error() string {
if len(e.Body) >= 1 {
s, ok := e.Body[0].(string)
@ -546,13 +553,14 @@ type transport interface {
SendMessage(*Message) error
}
var (
transports map[string]func(string) (transport, error) = make(map[string]func(string) (transport, error))
)
func getTransport(address string) (transport, error) {
var err error
var t transport
m := map[string]func(string) (transport, error){
"unix": newUnixTransport,
}
addresses := strings.Split(address, ";")
for _, v := range addresses {
i := strings.IndexRune(v, ':')
@ -560,7 +568,7 @@ func getTransport(address string) (transport, error) {
err = errors.New("dbus: invalid bus address (no transport)")
continue
}
f := m[v[:i]]
f := transports[v[:i]]
if f == nil {
err = errors.New("dbus: invalid bus address (invalid or unsupported transport)")
}

View File

@ -17,6 +17,12 @@ var IntrospectData = Interface{
},
}
// XML document type declaration of the introspection format version 1.0
const IntrospectDeclarationString = `
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
`
// The introspection data for the org.freedesktop.DBus.Introspectable interface,
// as a string.
const IntrospectDataString = `

View File

@ -4,6 +4,7 @@ import (
"encoding/xml"
"github.com/godbus/dbus"
"reflect"
"strings"
)
// Introspectable implements org.freedesktop.Introspectable.
@ -31,7 +32,7 @@ func NewIntrospectable(n *Node) Introspectable {
if err != nil {
panic(err)
}
return Introspectable(b)
return Introspectable(strings.TrimSpace(IntrospectDeclarationString) + string(b))
}
// Introspect implements org.freedesktop.Introspectable.Introspect.
@ -50,7 +51,7 @@ func Methods(v interface{}) []Method {
}
mt := t.Method(i).Type
if mt.NumOut() == 0 ||
mt.Out(mt.NumOut()-1) != reflect.TypeOf(&dbus.Error{"", nil}) {
mt.Out(mt.NumOut()-1) != reflect.TypeOf(&dbus.Error{}) {
continue
}

View File

@ -22,19 +22,19 @@ const (
// ErrIfaceNotFound is the error returned to peers who try to access properties
// on interfaces that aren't found.
var ErrIfaceNotFound = &dbus.Error{"org.freedesktop.DBus.Properties.Error.InterfaceNotFound", nil}
var ErrIfaceNotFound = dbus.NewError("org.freedesktop.DBus.Properties.Error.InterfaceNotFound", nil)
// ErrPropNotFound is the error returned to peers trying to access properties
// that aren't found.
var ErrPropNotFound = &dbus.Error{"org.freedesktop.DBus.Properties.Error.PropertyNotFound", nil}
var ErrPropNotFound = dbus.NewError("org.freedesktop.DBus.Properties.Error.PropertyNotFound", nil)
// ErrReadOnly is the error returned to peers trying to set a read-only
// property.
var ErrReadOnly = &dbus.Error{"org.freedesktop.DBus.Properties.Error.ReadOnly", nil}
var ErrReadOnly = dbus.NewError("org.freedesktop.DBus.Properties.Error.ReadOnly", nil)
// ErrInvalidArg is returned to peers if the type of the property that is being
// changed and the argument don't match.
var ErrInvalidArg = &dbus.Error{"org.freedesktop.DBus.Properties.Error.InvalidArg", nil}
var ErrInvalidArg = dbus.NewError("org.freedesktop.DBus.Properties.Error.InvalidArg", nil)
// The introspection data for the org.freedesktop.DBus.Properties interface.
var IntrospectData = introspect.Interface{

View File

@ -1,3 +1,5 @@
//+build !windows
package dbus
import (
@ -58,6 +60,10 @@ func newUnixTransport(keys string) (transport, error) {
}
}
func init() {
transports["unix"] = newUnixTransport
}
func (t *unixTransport) EnableUnixFDs() {
t.hasUnixFDs = true
}

View File

@ -0,0 +1,95 @@
// The UnixCredentials system call is currently only implemented on Linux
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
// https://golang.org/s/go1.4-syscall
// http://code.google.com/p/go/source/browse/unix/sockcmsg_linux.go?repo=sys
// Local implementation of the UnixCredentials system call for DragonFly BSD
package dbus
/*
#include <sys/ucred.h>
*/
import "C"
import (
"io"
"os"
"syscall"
"unsafe"
)
// http://golang.org/src/pkg/syscall/ztypes_linux_amd64.go
// http://golang.org/src/pkg/syscall/ztypes_dragonfly_amd64.go
type Ucred struct {
Pid int32
Uid uint32
Gid uint32
}
// http://golang.org/src/pkg/syscall/types_linux.go
// http://golang.org/src/pkg/syscall/types_dragonfly.go
// https://github.com/DragonFlyBSD/DragonFlyBSD/blob/master/sys/sys/ucred.h
const (
SizeofUcred = C.sizeof_struct_ucred
)
// http://golang.org/src/pkg/syscall/sockcmsg_unix.go
func cmsgAlignOf(salen int) int {
// From http://golang.org/src/pkg/syscall/sockcmsg_unix.go
//salign := sizeofPtr
// NOTE: It seems like 64-bit Darwin and DragonFly BSD kernels
// still require 32-bit aligned access to network subsystem.
//if darwin64Bit || dragonfly64Bit {
// salign = 4
//}
salign := 4
return (salen + salign - 1) & ^(salign - 1)
}
// http://golang.org/src/pkg/syscall/sockcmsg_unix.go
func cmsgData(h *syscall.Cmsghdr) unsafe.Pointer {
return unsafe.Pointer(uintptr(unsafe.Pointer(h)) + uintptr(cmsgAlignOf(syscall.SizeofCmsghdr)))
}
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
// UnixCredentials encodes credentials into a socket control message
// for sending to another process. This can be used for
// authentication.
func UnixCredentials(ucred *Ucred) []byte {
b := make([]byte, syscall.CmsgSpace(SizeofUcred))
h := (*syscall.Cmsghdr)(unsafe.Pointer(&b[0]))
h.Level = syscall.SOL_SOCKET
h.Type = syscall.SCM_CREDS
h.SetLen(syscall.CmsgLen(SizeofUcred))
*((*Ucred)(cmsgData(h))) = *ucred
return b
}
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
// ParseUnixCredentials decodes a socket control message that contains
// credentials in a Ucred structure. To receive such a message, the
// SO_PASSCRED option must be enabled on the socket.
func ParseUnixCredentials(m *syscall.SocketControlMessage) (*Ucred, error) {
if m.Header.Level != syscall.SOL_SOCKET {
return nil, syscall.EINVAL
}
if m.Header.Type != syscall.SCM_CREDS {
return nil, syscall.EINVAL
}
ucred := *(*Ucred)(unsafe.Pointer(&m.Data[0]))
return &ucred, nil
}
func (t *unixTransport) SendNullByte() error {
ucred := &Ucred{Pid: int32(os.Getpid()), Uid: uint32(os.Getuid()), Gid: uint32(os.Getgid())}
b := UnixCredentials(ucred)
_, oobn, err := t.UnixConn.WriteMsgUnix([]byte{0}, b, nil)
if err != nil {
return err
}
if oobn != len(b) {
return io.ErrShortWrite
}
return nil
}

View File

@ -1,4 +1,7 @@
// +build !darwin
// The UnixCredentials system call is currently only implemented on Linux
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
// https://golang.org/s/go1.4-syscall
// http://code.google.com/p/go/source/browse/unix/sockcmsg_linux.go?repo=sys
package dbus

View File

@ -4,6 +4,7 @@ import (
"bytes"
"fmt"
"reflect"
"sort"
"strconv"
)
@ -89,18 +90,27 @@ func (v Variant) format() (string, bool) {
return "{}", false
}
unamb := true
buf := bytes.NewBuffer([]byte("{"))
var buf bytes.Buffer
kvs := make([]string, rv.Len())
for i, k := range rv.MapKeys() {
s, b := MakeVariant(k.Interface()).format()
unamb = unamb && b
buf.Reset()
buf.WriteString(s)
buf.WriteString(": ")
s, b = MakeVariant(rv.MapIndex(k).Interface()).format()
unamb = unamb && b
buf.WriteString(s)
if i != rv.Len()-1 {
kvs[i] = buf.String()
}
buf.Reset()
buf.WriteByte('{')
sort.Strings(kvs)
for i, kv := range kvs {
if i > 0 {
buf.WriteString(", ")
}
buf.WriteString(kv)
}
buf.WriteByte('}')
return buf.String(), unamb

View File

@ -1,191 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, "control" means (i) the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
"Object" form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
"submitted" means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
2. Grant of Copyright License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
3. Grant of Patent License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
4. Redistribution.
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
You must give any other recipients of the Work or Derivative Works a copy of
this License; and
You must cause any modified files to carry prominent notices stating that You
changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
6. Trademarks.
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty.
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
8. Limitation of Liability.
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability.
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets "[]" replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same "printed page" as the copyright notice for easier identification within
third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,44 +0,0 @@
glog
====
Leveled execution logs for Go.
This is an efficient pure Go implementation of leveled logs in the
manner of the open source C++ package
http://code.google.com/p/google-glog
By binding methods to booleans it is possible to use the log package
without paying the expense of evaluating the arguments to the log.
Through the -vmodule flag, the package also provides fine-grained
control over logging at the file level.
The comment from glog.go introduces the ideas:
Package glog implements logging analogous to the Google-internal
C++ INFO/ERROR/V setup. It provides functions Info, Warning,
Error, Fatal, plus formatting variants such as Infof. It
also provides V-style logging controlled by the -v and
-vmodule=file=2 flags.
Basic examples:
glog.Info("Prepare to repel boarders")
glog.Fatalf("Initialization failed: %s", err)
See the documentation for the V function for an explanation
of these examples:
if glog.V(2) {
glog.Info("Starting transaction...")
}
glog.V(2).Infoln("Processed", nItems, "elements")
The repository contains an open source version of the log package
used inside Google. The master copy of the source lives inside
Google, not here. The code in this repo is for export only and is not itself
under development. Feature requests will be ignored.
Send bug reports to golang-nuts@googlegroups.com.

File diff suppressed because it is too large Load Diff

View File

@ -1,124 +0,0 @@
// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/
//
// Copyright 2013 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// File I/O for logs.
package glog
import (
"errors"
"flag"
"fmt"
"os"
"os/user"
"path/filepath"
"strings"
"sync"
"time"
)
// MaxSize is the maximum size of a log file in bytes.
var MaxSize uint64 = 1024 * 1024 * 1800
// logDirs lists the candidate directories for new log files.
var logDirs []string
// If non-empty, overrides the choice of directory in which to write logs.
// See createLogDirs for the full list of possible destinations.
var logDir = flag.String("log_dir", "", "If non-empty, write log files in this directory")
func createLogDirs() {
if *logDir != "" {
logDirs = append(logDirs, *logDir)
}
logDirs = append(logDirs, os.TempDir())
}
var (
pid = os.Getpid()
program = filepath.Base(os.Args[0])
host = "unknownhost"
userName = "unknownuser"
)
func init() {
h, err := os.Hostname()
if err == nil {
host = shortHostname(h)
}
current, err := user.Current()
if err == nil {
userName = current.Username
}
// Sanitize userName since it may contain filepath separators on Windows.
userName = strings.Replace(userName, `\`, "_", -1)
}
// shortHostname returns its argument, truncating at the first period.
// For instance, given "www.google.com" it returns "www".
func shortHostname(hostname string) string {
if i := strings.Index(hostname, "."); i >= 0 {
return hostname[:i]
}
return hostname
}
// logName returns a new log file name containing tag, with start time t, and
// the name for the symlink for tag.
func logName(tag string, t time.Time) (name, link string) {
name = fmt.Sprintf("%s.%s.%s.log.%s.%04d%02d%02d-%02d%02d%02d.%d",
program,
host,
userName,
tag,
t.Year(),
t.Month(),
t.Day(),
t.Hour(),
t.Minute(),
t.Second(),
pid)
return name, program + "." + tag
}
var onceLogDirs sync.Once
// create creates a new log file and returns the file and its filename, which
// contains tag ("INFO", "FATAL", etc.) and t. If the file is created
// successfully, create also attempts to update the symlink for that tag, ignoring
// errors.
func create(tag string, t time.Time) (f *os.File, filename string, err error) {
onceLogDirs.Do(createLogDirs)
if len(logDirs) == 0 {
return nil, "", errors.New("log: no log dirs")
}
name, link := logName(tag, t)
var lastErr error
for _, dir := range logDirs {
fname := filepath.Join(dir, name)
f, err := os.Create(fname)
if err == nil {
symlink := filepath.Join(dir, link)
os.Remove(symlink) // ignore err
os.Symlink(name, symlink) // ignore err
return f, fname, nil
}
lastErr = err
}
return nil, "", fmt.Errorf("log: cannot create log: %v", lastErr)
}

View File

@ -1,415 +0,0 @@
// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/
//
// Copyright 2013 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package glog
import (
"bytes"
"fmt"
stdLog "log"
"path/filepath"
"runtime"
"strconv"
"strings"
"testing"
"time"
)
// Test that shortHostname works as advertised.
func TestShortHostname(t *testing.T) {
for hostname, expect := range map[string]string{
"": "",
"host": "host",
"host.google.com": "host",
} {
if got := shortHostname(hostname); expect != got {
t.Errorf("shortHostname(%q): expected %q, got %q", hostname, expect, got)
}
}
}
// flushBuffer wraps a bytes.Buffer to satisfy flushSyncWriter.
type flushBuffer struct {
bytes.Buffer
}
func (f *flushBuffer) Flush() error {
return nil
}
func (f *flushBuffer) Sync() error {
return nil
}
// swap sets the log writers and returns the old array.
func (l *loggingT) swap(writers [numSeverity]flushSyncWriter) (old [numSeverity]flushSyncWriter) {
l.mu.Lock()
defer l.mu.Unlock()
old = l.file
for i, w := range writers {
logging.file[i] = w
}
return
}
// newBuffers sets the log writers to all new byte buffers and returns the old array.
func (l *loggingT) newBuffers() [numSeverity]flushSyncWriter {
return l.swap([numSeverity]flushSyncWriter{new(flushBuffer), new(flushBuffer), new(flushBuffer), new(flushBuffer)})
}
// contents returns the specified log value as a string.
func contents(s severity) string {
return logging.file[s].(*flushBuffer).String()
}
// contains reports whether the string is contained in the log.
func contains(s severity, str string, t *testing.T) bool {
return strings.Contains(contents(s), str)
}
// setFlags configures the logging flags how the test expects them.
func setFlags() {
logging.toStderr = false
}
// Test that Info works as advertised.
func TestInfo(t *testing.T) {
setFlags()
defer logging.swap(logging.newBuffers())
Info("test")
if !contains(infoLog, "I", t) {
t.Errorf("Info has wrong character: %q", contents(infoLog))
}
if !contains(infoLog, "test", t) {
t.Error("Info failed")
}
}
func TestInfoDepth(t *testing.T) {
setFlags()
defer logging.swap(logging.newBuffers())
f := func() { InfoDepth(1, "depth-test1") }
// The next three lines must stay together
_, _, wantLine, _ := runtime.Caller(0)
InfoDepth(0, "depth-test0")
f()
msgs := strings.Split(strings.TrimSuffix(contents(infoLog), "\n"), "\n")
if len(msgs) != 2 {
t.Fatalf("Got %d lines, expected 2", len(msgs))
}
for i, m := range msgs {
if !strings.HasPrefix(m, "I") {
t.Errorf("InfoDepth[%d] has wrong character: %q", i, m)
}
w := fmt.Sprintf("depth-test%d", i)
if !strings.Contains(m, w) {
t.Errorf("InfoDepth[%d] missing %q: %q", i, w, m)
}
// pull out the line number (between : and ])
msg := m[strings.LastIndex(m, ":")+1:]
x := strings.Index(msg, "]")
if x < 0 {
t.Errorf("InfoDepth[%d]: missing ']': %q", i, m)
continue
}
line, err := strconv.Atoi(msg[:x])
if err != nil {
t.Errorf("InfoDepth[%d]: bad line number: %q", i, m)
continue
}
wantLine++
if wantLine != line {
t.Errorf("InfoDepth[%d]: got line %d, want %d", i, line, wantLine)
}
}
}
func init() {
CopyStandardLogTo("INFO")
}
// Test that CopyStandardLogTo panics on bad input.
func TestCopyStandardLogToPanic(t *testing.T) {
defer func() {
if s, ok := recover().(string); !ok || !strings.Contains(s, "LOG") {
t.Errorf(`CopyStandardLogTo("LOG") should have panicked: %v`, s)
}
}()
CopyStandardLogTo("LOG")
}
// Test that using the standard log package logs to INFO.
func TestStandardLog(t *testing.T) {
setFlags()
defer logging.swap(logging.newBuffers())
stdLog.Print("test")
if !contains(infoLog, "I", t) {
t.Errorf("Info has wrong character: %q", contents(infoLog))
}
if !contains(infoLog, "test", t) {
t.Error("Info failed")
}
}
// Test that the header has the correct format.
func TestHeader(t *testing.T) {
setFlags()
defer logging.swap(logging.newBuffers())
defer func(previous func() time.Time) { timeNow = previous }(timeNow)
timeNow = func() time.Time {
return time.Date(2006, 1, 2, 15, 4, 5, .067890e9, time.Local)
}
pid = 1234
Info("test")
var line int
format := "I0102 15:04:05.067890 1234 glog_test.go:%d] test\n"
n, err := fmt.Sscanf(contents(infoLog), format, &line)
if n != 1 || err != nil {
t.Errorf("log format error: %d elements, error %s:\n%s", n, err, contents(infoLog))
}
// Scanf treats multiple spaces as equivalent to a single space,
// so check for correct space-padding also.
want := fmt.Sprintf(format, line)
if contents(infoLog) != want {
t.Errorf("log format error: got:\n\t%q\nwant:\t%q", contents(infoLog), want)
}
}
// Test that an Error log goes to Warning and Info.
// Even in the Info log, the source character will be E, so the data should
// all be identical.
func TestError(t *testing.T) {
setFlags()
defer logging.swap(logging.newBuffers())
Error("test")
if !contains(errorLog, "E", t) {
t.Errorf("Error has wrong character: %q", contents(errorLog))
}
if !contains(errorLog, "test", t) {
t.Error("Error failed")
}
str := contents(errorLog)
if !contains(warningLog, str, t) {
t.Error("Warning failed")
}
if !contains(infoLog, str, t) {
t.Error("Info failed")
}
}
// Test that a Warning log goes to Info.
// Even in the Info log, the source character will be W, so the data should
// all be identical.
func TestWarning(t *testing.T) {
setFlags()
defer logging.swap(logging.newBuffers())
Warning("test")
if !contains(warningLog, "W", t) {
t.Errorf("Warning has wrong character: %q", contents(warningLog))
}
if !contains(warningLog, "test", t) {
t.Error("Warning failed")
}
str := contents(warningLog)
if !contains(infoLog, str, t) {
t.Error("Info failed")
}
}
// Test that a V log goes to Info.
func TestV(t *testing.T) {
setFlags()
defer logging.swap(logging.newBuffers())
logging.verbosity.Set("2")
defer logging.verbosity.Set("0")
V(2).Info("test")
if !contains(infoLog, "I", t) {
t.Errorf("Info has wrong character: %q", contents(infoLog))
}
if !contains(infoLog, "test", t) {
t.Error("Info failed")
}
}
// Test that a vmodule enables a log in this file.
func TestVmoduleOn(t *testing.T) {
setFlags()
defer logging.swap(logging.newBuffers())
logging.vmodule.Set("glog_test=2")
defer logging.vmodule.Set("")
if !V(1) {
t.Error("V not enabled for 1")
}
if !V(2) {
t.Error("V not enabled for 2")
}
if V(3) {
t.Error("V enabled for 3")
}
V(2).Info("test")
if !contains(infoLog, "I", t) {
t.Errorf("Info has wrong character: %q", contents(infoLog))
}
if !contains(infoLog, "test", t) {
t.Error("Info failed")
}
}
// Test that a vmodule of another file does not enable a log in this file.
func TestVmoduleOff(t *testing.T) {
setFlags()
defer logging.swap(logging.newBuffers())
logging.vmodule.Set("notthisfile=2")
defer logging.vmodule.Set("")
for i := 1; i <= 3; i++ {
if V(Level(i)) {
t.Errorf("V enabled for %d", i)
}
}
V(2).Info("test")
if contents(infoLog) != "" {
t.Error("V logged incorrectly")
}
}
// vGlobs are patterns that match/don't match this file at V=2.
var vGlobs = map[string]bool{
// Easy to test the numeric match here.
"glog_test=1": false, // If -vmodule sets V to 1, V(2) will fail.
"glog_test=2": true,
"glog_test=3": true, // If -vmodule sets V to 1, V(3) will succeed.
// These all use 2 and check the patterns. All are true.
"*=2": true,
"?l*=2": true,
"????_*=2": true,
"??[mno]?_*t=2": true,
// These all use 2 and check the patterns. All are false.
"*x=2": false,
"m*=2": false,
"??_*=2": false,
"?[abc]?_*t=2": false,
}
// Test that vmodule globbing works as advertised.
func testVmoduleGlob(pat string, match bool, t *testing.T) {
setFlags()
defer logging.swap(logging.newBuffers())
defer logging.vmodule.Set("")
logging.vmodule.Set(pat)
if V(2) != Verbose(match) {
t.Errorf("incorrect match for %q: got %t expected %t", pat, V(2), match)
}
}
// Test that a vmodule globbing works as advertised.
func TestVmoduleGlob(t *testing.T) {
for glob, match := range vGlobs {
testVmoduleGlob(glob, match, t)
}
}
func TestRollover(t *testing.T) {
setFlags()
var err error
defer func(previous func(error)) { logExitFunc = previous }(logExitFunc)
logExitFunc = func(e error) {
err = e
}
defer func(previous uint64) { MaxSize = previous }(MaxSize)
MaxSize = 512
Info("x") // Be sure we have a file.
info, ok := logging.file[infoLog].(*syncBuffer)
if !ok {
t.Fatal("info wasn't created")
}
if err != nil {
t.Fatalf("info has initial error: %v", err)
}
fname0 := info.file.Name()
Info(strings.Repeat("x", int(MaxSize))) // force a rollover
if err != nil {
t.Fatalf("info has error after big write: %v", err)
}
// Make sure the next log file gets a file name with a different
// time stamp.
//
// TODO: determine whether we need to support subsecond log
// rotation. C++ does not appear to handle this case (nor does it
// handle Daylight Savings Time properly).
time.Sleep(1 * time.Second)
Info("x") // create a new file
if err != nil {
t.Fatalf("error after rotation: %v", err)
}
fname1 := info.file.Name()
if fname0 == fname1 {
t.Errorf("info.f.Name did not change: %v", fname0)
}
if info.nbytes >= MaxSize {
t.Errorf("file size was not reset: %d", info.nbytes)
}
}
func TestLogBacktraceAt(t *testing.T) {
setFlags()
defer logging.swap(logging.newBuffers())
// The peculiar style of this code simplifies line counting and maintenance of the
// tracing block below.
var infoLine string
setTraceLocation := func(file string, line int, ok bool, delta int) {
if !ok {
t.Fatal("could not get file:line")
}
_, file = filepath.Split(file)
infoLine = fmt.Sprintf("%s:%d", file, line+delta)
err := logging.traceLocation.Set(infoLine)
if err != nil {
t.Fatal("error setting log_backtrace_at: ", err)
}
}
{
// Start of tracing block. These lines know about each other's relative position.
_, file, line, ok := runtime.Caller(0)
setTraceLocation(file, line, ok, +2) // Two lines between Caller and Info calls.
Info("we want a stack trace here")
}
numAppearances := strings.Count(contents(infoLog), infoLine)
if numAppearances < 2 {
// Need 2 appearances, one in the log header and one in the trace:
// log_test.go:281: I0511 16:36:06.952398 02238 log_test.go:280] we want a stack trace here
// ...
// github.com/glog/glog_test.go:280 (0x41ba91)
// ...
// We could be more precise but that would require knowing the details
// of the traceback format, which may not be dependable.
t.Fatal("got no trace back; log is ", contents(infoLog))
}
}
func BenchmarkHeader(b *testing.B) {
for i := 0; i < b.N; i++ {
buf, _, _ := logging.header(infoLog, 0)
logging.putBuffer(buf)
}
}

View File

@ -60,7 +60,8 @@ type Capabilities interface {
Apply(kind CapType) error
}
// NewPid create new initialized Capabilities object for given pid.
// NewPid create new initialized Capabilities object for given pid when it
// is nonzero, or for the current pid if pid is 0
func NewPid(pid int) (Capabilities, error) {
return newPid(pid)
}

View File

@ -351,7 +351,15 @@ func (c *capsV3) Load() (err error) {
return
}
f, err := os.Open(fmt.Sprintf("/proc/%d/status", c.hdr.pid))
var status_path string
if c.hdr.pid == 0 {
status_path = fmt.Sprintf("/proc/self/status")
} else {
status_path = fmt.Sprintf("/proc/%d/status", c.hdr.pid)
}
f, err := os.Open(status_path)
if err != nil {
return
}