~nilium/clog

da91958e7d2fed5cb36503525a7ffdb07e9e2379 — Noel Cower 3 years ago 888d00e
Fork clog for personal use

This is a fork of clog for personal use. It didn't seem worthwhile to
reimplement this when I was able to open source it before leaving
Kochava. The main reason for forking is that I would like the ability
to make breaking changes to the library that I don't think Kochava has
any need for (the state I left the original in is stable). Currently,
the main difference here is the removal of all the constructor
functions, because they don't have any real benefit over using what Zap
already provides via its level flags.
8 files changed, 57 insertions(+), 364 deletions(-)

D .circleci/config.yml
M README.md
M clog.go
D clog_test.go
M example_test.go
M go.mod
M go.sum
D stdlog.go
D .circleci/config.yml => .circleci/config.yml +0 -13
@@ 1,13 0,0 @@
version: '2.1'

jobs:
  build:
    docker:
      - image: circleci/golang:1.12
    working_directory: ~/build
    environment:
      GO111MODULE: 'on'
    steps:
    - checkout
    - run: go test -coverprofile=cover.out -covermode=atomic .
    - run: go tool cover -func=cover.out

M README.md => README.md +7 -17
@@ 1,19 1,21 @@
clog
===

[![GoDoc](https://godoc.org/github.com/Kochava/clog?status.svg)](https://godoc.org/github.com/Kochava/clog)
[![GoDoc](https://pkg.go.dev/go.spiff.io/clog?status.svg)](https://pkg.go.dev/go.spiff.io/clog)

    go get github.com/Kochava/clog
    go get go.spiff.io/clog

clog is a simple package for initializing a [Zap][] logger and attaching it to
a context, along with functions for logging from the context-attached logger or
associating new fields to the logger.
clog is a simple package for attaching a [Zap][] logger to a context, along with
functions for logging from the context-attached logger or associating new fields
to the logger.

Generally speaking this is a bad use of the context package, but utility won out
over passing both a context and a logger around all the time. In particular,
this is useful for passing a request-scoped logger through different
http.Handler implementations that otherwise do not support Zap.

This is a fork of <https://github.com/Kochava/clog>.

[Zap]: https://go.uber.org/zap




@@ 22,18 24,6 @@ Usage

A few examples of basic usage follow.

### Initialize a logger

```go
// Create a logger at info level with a production configuration.
level := zap.NewAtomicLevelAt(zap.InfoLevel)
l, err := clog.New(level, false)
if err != nil {
    panic(err)
}
l.Info("Ready")
```

### Attach a logger to a context

```go

M clog.go => clog.go +0 -98
@@ 1,101 1,3 @@
// Package clog is a convenience package for passing Zap loggers through
// contexts.
package clog

import (
	"os"
	"path"
	"path/filepath"
	"strings"
	"unicode"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

var zeroLevel = zap.AtomicLevel{}

var osGetenv = os.Getenv

var osArg0 = func() string {
	return os.Args[0]
}

func sanitizeEnvRune(r rune) rune {
	r = unicode.ToUpper(r)
	if r == '_' || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') {
		return r
	}
	return -1
}

func envPrefix(procname string) string {
	if procname == "" && len(os.Args) > 0 {
		procname = filepath.ToSlash(osArg0())
	}
	procname = filepath.Base(procname)
	if ext := path.Ext(procname); ext != "" {
		procname = procname[:len(procname)-len(ext)]
	}
	procname = strings.Map(sanitizeEnvRune, procname)
	if procname == "" {
		return ""
	}
	return procname + "_"
}

// GetEnvConfig returns the environment-configured logging level and whether to use JSON and debug
// logging for procname. If procname is the empty string, os.Args[0] is used instead.
//
// If PROCNAME_LOG_MODE is set to "dev" (case-insensitive) then log output will be formatted for
// reading on a console. Otherwise, logging defaults to a production configuration.
//
// If PROCNAME_LOG_LEVEL is set to a valid Zap logging level (info, warn, etc.), then that logging
// level will be returned. Otherwise, the logging level defaults to zap.InfoLevel for production and
// zap.DebugLevel for development.
func GetEnvConfig(procname string) (level zapcore.Level, isDev bool) {
	const devEnvironment = "dev"
	prefix := envPrefix(procname)
	isDev = strings.EqualFold(osGetenv(prefix+"LOG_MODE"), devEnvironment)
	levelText := osGetenv(prefix + "LOG_LEVEL")
	if levelText != "" && level.UnmarshalText([]byte(levelText)) == nil {
		// nop
	} else if isDev {
		level = zap.DebugLevel // development
	} else {
		level = zap.InfoLevel // production

	}
	return
}

// NewFromEnv allocates a new zap.Logger using configuration from the environment.
// This looks for PROCNAME_LOG_MODE and PROCNAME_LOG_LEVEL to configure the logger.
// If LOG_MODE is not "dev", the development configuration of Zap is used.
// Otherwise, logging is configured for production.
func NewFromEnv(procname string, level zap.AtomicLevel) (*zap.Logger, error) {
	lvl, isDev := GetEnvConfig(procname)
	if level != zeroLevel {
		level.SetLevel(lvl)
	}
	return New(level, isDev)
}

// New allocates a new zap.Logger using configuration based on the level given and the json and
// debug parameters, as interpreted by Config.
func New(level zap.AtomicLevel, isDev bool) (*zap.Logger, error) {
	return Config(level, isDev).Build()
}

// Config returns a zap.Config based on the level given and the json and debug parameters. If json
// is true, the config uses a JSON encoder. If debug is true, production limits on logging are
// removed and the development flag is set to true.
func Config(level zap.AtomicLevel, isDev bool) (conf zap.Config) {
	if isDev {
		conf = zap.NewDevelopmentConfig()
	} else {
		conf = zap.NewProductionConfig()
	}
	conf.Level = level
	return conf
}

D clog_test.go => clog_test.go +0 -144
@@ 1,144 0,0 @@
package clog

import (
	"sync"
	"testing"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

var arg0Lock sync.Mutex

func setArg0(t *testing.T, v string) func() {
	arg0Lock.Lock()
	cur := osArg0
	osArg0 = func() string {
		t.Logf("Requested os.Args[0] (%q)", v)
		return v
	}
	return func() { defer arg0Lock.Unlock(); osArg0 = cur }
}

var getenvLock sync.Mutex

func setGetenv(t *testing.T, values map[string]string) func() {
	getenvLock.Lock()
	cur := osGetenv
	osGetenv = func(k string) string {
		t.Logf("Requested os.Getenv(%q)", values[k])
		return values[k]
	}
	return func() { defer getenvLock.Unlock(); osGetenv = cur }
}

func TestEnvPrefix(t *testing.T) {
	cases := []struct {
		In, Want string
	}{
		{"./Foo_", "FOO__"},
		{"./Foo-", "FOO_"},
		{"/usr/sbin/health-checker", "HEALTHCHECKER_"},
		{"/usr/sbin/health_checker", "HEALTH_CHECKER_"},
		{"foo_bar", "FOO_BAR_"},
	}

	for _, c := range cases {
		t.Run("ProcnamePass="+c.In, func(t *testing.T) {
			c := c
			want, got := c.Want, envPrefix(c.In)
			if want != got {
				t.Errorf("envPrefix(%q) = %q; want %q", c.In, got, want)
			}
		})
	}

	for _, c := range cases {
		c := c
		t.Run("ProcnameArg0="+c.In, func(t *testing.T) {
			defer setArg0(t, c.In)()
			want, got := c.Want, envPrefix("")
			if want != got {
				t.Errorf("envPrefix(%q) = %q; want %q", c.In, got, want)
			}
		})
	}

	t.Run("EmptyPass", func(t *testing.T) {
		const (
			in   = "/usr/bin/clog-daemon"
			want = "CLOGDAEMON_"
		)
		defer setArg0(t, in)()
		if got := envPrefix(""); want != got {
			t.Errorf("envPrefix(%q) = %q; want %q", in, got, want)
		}
	})

	t.Run("NoValues", func(t *testing.T) {
		const (
			in   = ""
			want = ""
		)
		defer setArg0(t, in)()
		if got := envPrefix(""); want != got {
			t.Errorf("envPrefix(%q) = %q; want %q", in, got, want)
		}
	})
}

func TestGetEnvConfig(t *testing.T) {
	type values map[string]string
	cases := []struct {
		Case   string
		Arg0   string
		Values values
		Level  zapcore.Level
		IsDev  bool
	}{
		{
			"Defaults",
			"",
			nil,
			zap.InfoLevel,
			false,
		},
		{
			"EmptyName",
			"",
			values{"LOG_MODE": "dev", "LOG_LEVEL": "warn"},
			zap.WarnLevel,
			true,
		},
		{
			"InvalidLevel",
			"",
			values{"LOG_MODE": "prod", "LOG_LEVEL": "WARNING"},
			zap.InfoLevel,
			false,
		},
		{
			"NamedProc",
			"/usr/local/bin/daemon",
			values{"DAEMON_LOG_MODE": "dev", "DAEMON_LOG_LEVEL": "fatal"},
			zap.FatalLevel,
			true,
		},
	}

	for _, c := range cases {
		c := c
		t.Run(c.Case, func(t *testing.T) {
			defer setArg0(t, c.Arg0)()
			defer setGetenv(t, c.Values)()

			level, isDev := GetEnvConfig("")
			if level != c.Level {
				t.Errorf("level = %v; want %v", level, c.Level)
			}
			if isDev != c.IsDev {
				t.Errorf("isDev = %t; want %t", isDev, c.IsDev)
			}
		})
	}
}

M example_test.go => example_test.go +2 -3
@@ 3,14 3,13 @@ package clog_test
import (
	"context"

	"github.com/Kochava/clog"
	"go.spiff.io/clog"
	"go.uber.org/zap"
)

func ExampleNew() {
	// Create a logger at info level with a production configuration.
	level := zap.NewAtomicLevelAt(zap.InfoLevel)
	l, err := clog.New(level, false)
	l, err := zap.NewProduction()
	if err != nil {
		panic(err)
	}

M go.mod => go.mod +3 -6
@@ 1,11 1,8 @@
module github.com/Kochava/clog
module go.spiff.io/clog

go 1.12
go 1.15

require (
	github.com/pkg/errors v0.9.1 // indirect
	github.com/stretchr/testify v1.4.0 // indirect
	go.uber.org/atomic v1.3.2 // indirect
	go.uber.org/multierr v1.1.0 // indirect
	go.uber.org/zap v1.9.1
	go.uber.org/zap v1.16.0
)

M go.sum => go.sum +45 -6
@@ 1,19 1,58 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM=
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=

D stdlog.go => stdlog.go +0 -77
@@ 1,77 0,0 @@
package clog

import (
	"fmt"
	"strings"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

// StdLogger is a logger that implements most methods used by "log" stdlib users. Print-ed logs are
// recorded at Info level.
type StdLogger struct{ log *zap.Logger }

// NewStdLogger creats a new StdLogger attached to the given zap.Logger. If logger is nil,
// NewStdLogger returns nil as well.
func NewStdLogger(logger *zap.Logger) *StdLogger {
	if logger == nil {
		return nil
	}
	return &StdLogger{logger.WithOptions(zap.AddCallerSkip(1))}
}

// With returns a copy of this StdLogger with additional Zap fields.
func (l *StdLogger) With(fields ...zapcore.Field) *StdLogger {
	return &StdLogger{l.log.With(fields...)}
}

// WithOptions returns a copy of this StdLogger with additional Zap options.
func (l *StdLogger) WithOptions(opts ...zap.Option) *StdLogger {
	return &StdLogger{l.log.WithOptions(opts...)}
}

// Panic writes a panic-level fmt.Sprint-formatted log message.
func (l *StdLogger) Panic(args ...interface{}) {
	l.log.Panic(fmt.Sprint(args...))
}

// Panicln writes a panic-level fmt.Sprintln-formatted log message.
func (l *StdLogger) Panicln(args ...interface{}) {
	l.log.Panic(strings.TrimSuffix(fmt.Sprintln(args...), "\n"))
}

// Panicf writes a panic-level fmt.Sprintf-formatted log message.
func (l *StdLogger) Panicf(format string, args ...interface{}) {
	l.log.Panic(fmt.Sprintf(format, args...))
}

// Fatal writes a fatal-level fmt.Sprint-formatted log message.
func (l *StdLogger) Fatal(args ...interface{}) {
	l.log.Fatal(fmt.Sprint(args...))
}

// Fatalln writes a fatal-level fmt.Sprintln-formatted log message.
func (l *StdLogger) Fatalln(args ...interface{}) {
	l.log.Fatal(strings.TrimSuffix(fmt.Sprintln(args...), "\n"))
}

// Fatalf writes a fatal-level fmt.Sprintf-formatted log message.
func (l *StdLogger) Fatalf(format string, args ...interface{}) {
	l.log.Fatal(fmt.Sprintf(format, args...))
}

// Print writes an info-level fmt.Sprint-formatted log message.
func (l *StdLogger) Print(args ...interface{}) {
	l.log.Info(fmt.Sprint(args...))
}

// Println writes an info-level fmt.Sprintln-formatted log message.
func (l *StdLogger) Println(args ...interface{}) {
	l.log.Info(strings.TrimSuffix(fmt.Sprintln(args...), "\n"))
}

// Printf writes an info-level fmt.Sprintf-formatted log message.
func (l *StdLogger) Printf(format string, args ...interface{}) {
	l.log.Info(fmt.Sprintf(format, args...))
}