From e927c36f8a5f30029f36db4892fa9c67f7dbfbd4 Mon Sep 17 00:00:00 2001 From: Unknwon Date: Sat, 9 Nov 2019 16:22:47 -0800 Subject: [PATCH] Remove init register pattern (#11) * Remove init register pattern * codecov: rename config file --- README.md | 49 ++++++------ clog.go | 3 - clog_test.go | 83 ++++++++++----------- .codecov.yml => codecov.yml | 0 console.go | 54 ++++++++------ console_test.go | 24 +++--- discord.go | 57 ++++++++------ discord_test.go | 30 ++++++-- file.go | 55 ++++++++------ file_test.go | 24 ++++-- go.mod | 2 +- logger.go | 80 ++++++++++---------- logger_test.go | 144 +++++++++--------------------------- slack.go | 56 ++++++++------ slack_test.go | 30 ++++++-- 15 files changed, 347 insertions(+), 344 deletions(-) rename .codecov.yml => codecov.yml (100%) diff --git a/README.md b/README.md index 89cf322..876b775 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ import ( ) func init() { - err := log.New(log.ModeConsole) + err := log.NewConsole() if err != nil { panic("unable to create new logger: " + err.Error()) } @@ -48,7 +48,7 @@ The code inside `init` function is equivalent to the following: ```go func init() { - err := log.New(log.ModeConsole, 0, log.ConsoleConfig{ + err := log.NewConsole(0, log.ConsoleConfig{ Level: log.LevelTrace, }) if err != nil { @@ -67,7 +67,7 @@ In production, you may want to make log less verbose and be asynchronous: func init() { // The buffer size mainly depends on number of logs could be produced at the same time, // 100 is a good default. - err := log.New(log.ModeConsole, 100, log.ConsoleConfig{ + err := log.NewConsole(100, log.ConsoleConfig{ Level: log.LevelInfo, }) if err != nil { @@ -79,7 +79,7 @@ func init() { - When you set level to be `LevelInfo`, calls to the `log.Trace` will be simply noop. - The console logger comes with color output, but for non-colorable destination, the color output will be disabled automatically. -Other builtin loggers are file (`log.ModeFile`), Slack (`log.ModeSlack`) and Discord (`log.ModeDiscord`), see later sections in the documentation for usage details. +Other builtin loggers are file (`log.NewFile`), Slack (`log.NewSlack`) and Discord (`log.NewDiscord`), see later sections in the documentation for usage details. ### Multiple Loggers @@ -87,11 +87,11 @@ You can have multiple loggers in different modes across levels. ```go func init() { - err := log.New(log.ModeConsole) + err := log.NewConsole() if err != nil { panic("unable to create new logger: " + err.Error()) } - err := log.New(log.ModeFile, log.FileConfig{ + err := log.NewFile(log.FileConfig{ Level: log.LevelInfo, Filename: "clog.log", }) @@ -131,7 +131,7 @@ File logger is the single most powerful builtin logger, it has the ability to ro ```go func init() { - err := log.New(log.ModeFile, 100, log.FileConfig{ + err := log.NewFile(100, log.FileConfig{ Level: log.LevelInfo, Filename: "clog.log", FileRotationConfig: log.FileRotationConfig { @@ -165,7 +165,7 @@ Slack logger is also supported in a simple way: ```go func init() { - err := log.New(log.ModeSlack, 100, log.SlackConfig{ + err := log.NewSlack(100, log.SlackConfig{ Level: log.LevelInfo, URL: "https://url-to-slack-webhook", }) @@ -183,7 +183,7 @@ Discord logger is supported in rich format via [Embed Object](https://discordapp ```go func init() { - err := log.New(log.ModeDiscord, 100, log.DiscordConfig{ + err := log.NewDiscord(100, log.DiscordConfig{ Level: log.LevelInfo, URL: "https://url-to-discord-webhook", }) @@ -204,8 +204,6 @@ Here is an example which sends all logs to a channel, we call it `chanLogger` he ```go import log "unknwon.dev/clog/v2" -const modeChannel log.Mode = "channel" - type chanConfig struct { c chan string } @@ -213,35 +211,38 @@ type chanConfig struct { var _ log.Logger = (*chanLogger)(nil) type chanLogger struct { + name string level log.Level c chan string } -func (*chanLogger) Mode() log.Mode { return modeChannel } +func (l *chanLogger) Name() string { return l.name } func (l *chanLogger) Level() log.Level { return l.level } -func (l *chanLogger) Write(m Messager) error { +func (l *chanLogger) Write(m log.Messager) error { l.c <- m.String() return nil } -func init() { - log.NewRegister(modeChannel, func(v interface{}) (log.Logger, error) { - if v == nil { - v = chanConfig{} - } - - cfg, ok := v.(chanConfig) - if !ok { - return nil, fmt.Errorf("invalid config object: want %T got %T", chanConfig{}, v) +func main() { + log.New("channel", func(name string, vs ...interface{}) (log.Logger, error) { + var cfg *chanLogger + for i := range vs { + switch v := vs[i].(type) { + case chanLogger: + cfg = &v + } } - if cfg.c == nil { + if cfg == nil { + return nil, fmt.Errorf("config object with the type '%T' not found", chanLogger{}) + } else if cfg.c == nil { return nil, errors.New("channel is nil") } return &chanLogger{ - c: cfg.c, + name: name, + c: cfg.c, }, nil }) } diff --git a/clog.go b/clog.go index efce2f9..a7bcd1e 100644 --- a/clog.go +++ b/clog.go @@ -6,9 +6,6 @@ import ( "os" ) -// Mode is the output source. -type Mode string - // Level is the logging level. type Level int diff --git a/clog_test.go b/clog_test.go index 8c9c3b6..e363fbf 100644 --- a/clog_test.go +++ b/clog_test.go @@ -37,37 +37,47 @@ func (l *chanLogger) Write(m Messager) error { } func Test_chanLogger(t *testing.T) { - mode1 := Mode("mode1") - level1 := LevelTrace - NewRegister(mode1, func(v interface{}) (Logger, error) { - cfg, ok := v.(chanConfig) - if !ok { - return nil, fmt.Errorf("invalid config object: want %T got %T", chanConfig{}, v) - } - return &chanLogger{ - c: cfg.c, - noopLogger: &noopLogger{ - mode: mode1, - level: level1, - }, - }, nil - }) - - mode2 := Mode("mode2") - level2 := LevelError - NewRegister(mode2, func(v interface{}) (Logger, error) { - cfg, ok := v.(chanConfig) - if !ok { - return nil, fmt.Errorf("invalid config object: want %T got %T", &chanConfig{}, v) + initer := func(name string, level Level) Initer { + return func(_ string, vs ...interface{}) (Logger, error) { + var cfg *chanConfig + for i := range vs { + switch v := vs[i].(type) { + case chanConfig: + cfg = &v + } + } + + if cfg == nil { + return nil, fmt.Errorf("config object with the type '%T' not found", chanConfig{}) + } + + return &chanLogger{ + c: cfg.c, + noopLogger: &noopLogger{ + name: name, + level: level, + }, + }, nil } - return &chanLogger{ - c: cfg.c, - noopLogger: &noopLogger{ - mode: mode2, - level: level2, - }, - }, nil - }) + } + + test1 := "mode1" + test1Initer := initer(test1, LevelTrace) + + test2 := "mode2" + test2Initer := initer(test2, LevelError) + + c1 := make(chan string) + c2 := make(chan string) + + defer Remove(test1) + defer Remove(test2) + assert.Nil(t, New(test1, test1Initer, 1, chanConfig{ + c: c1, + })) + assert.Nil(t, New(test2, test2Initer, 1, chanConfig{ + c: c2, + })) tests := []struct { name string @@ -106,19 +116,6 @@ func Test_chanLogger(t *testing.T) { containsStr2: "()] log message", }, } - - c1 := make(chan string) - c2 := make(chan string) - - defer Remove(mode1) - defer Remove(mode2) - assert.Nil(t, New(mode1, 1, chanConfig{ - c: c1, - })) - assert.Nil(t, New(mode2, 1, chanConfig{ - c: c2, - })) - for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert.Equal(t, 2, mgr.len()) diff --git a/.codecov.yml b/codecov.yml similarity index 100% rename from .codecov.yml rename to codecov.yml diff --git a/console.go b/console.go index eba7b02..8fe4258 100644 --- a/console.go +++ b/console.go @@ -1,16 +1,12 @@ package clog import ( - "fmt" "log" "github.com/fatih/color" ) -// ModeConsole is used to indicate console logger. -const ModeConsole Mode = "console" - -// Console color set for different levels. +// consoleColors is the color set for different levels. var consoleColors = []func(a ...interface{}) string{ color.New(color.FgBlue).SprintFunc(), // Trace color.New(color.FgGreen).SprintFunc(), // Info @@ -28,37 +24,51 @@ type ConsoleConfig struct { var _ Logger = (*consoleLogger)(nil) type consoleLogger struct { - level Level + *noopLogger *log.Logger } -func (*consoleLogger) Mode() Mode { - return ModeConsole +func (l *consoleLogger) Write(m Messager) error { + l.Print(consoleColors[m.Level()](m.String())) + return nil } -func (l *consoleLogger) Level() Level { - return l.level +// DefaultConsoleName is the default name for the console logger. +const DefaultConsoleName = "console" + +// NewConsole initializes and appends a new console logger with default name +// to the managed list. +func NewConsole(vs ...interface{}) error { + return NewConsoleWithName(DefaultConsoleName, vs...) } -func (l *consoleLogger) Write(m Messager) error { - l.Print(consoleColors[m.Level()](m.String())) - return nil +// NewConsoleWithName initializes and appends a new console logger with given +// name to the managed list. +func NewConsoleWithName(name string, vs ...interface{}) error { + return New(name, ConsoleIniter(), vs...) } -func init() { - NewRegister(ModeConsole, func(v interface{}) (Logger, error) { - if v == nil { - v = ConsoleConfig{} +// ConsoleIniter returns the initer for the console logger. +func ConsoleIniter() Initer { + return func(name string, vs ...interface{}) (Logger, error) { + var cfg *ConsoleConfig + for i := range vs { + switch v := vs[i].(type) { + case ConsoleConfig: + cfg = &v + } } - cfg, ok := v.(ConsoleConfig) - if !ok { - return nil, fmt.Errorf("invalid config object: want %T got %T", ConsoleConfig{}, v) + if cfg == nil { + cfg = &ConsoleConfig{} } return &consoleLogger{ - level: cfg.Level, + noopLogger: &noopLogger{ + name: name, + level: cfg.Level, + }, Logger: log.New(color.Output, "", log.Ldate|log.Ltime), }, nil - }) + } } diff --git a/console_test.go b/console_test.go index b4d6b31..d9bcdea 100644 --- a/console_test.go +++ b/console_test.go @@ -1,45 +1,51 @@ package clog import ( - "errors" "testing" "github.com/stretchr/testify/assert" ) -func Test_ModeConsole(t *testing.T) { - defer Remove(ModeConsole) +func Test_consoleLogger(t *testing.T) { + testName := "Test_consoleLogger" + defer Remove(DefaultConsoleName) + defer Remove(testName) tests := []struct { name string + mode string config interface{} wantLevel Level wantErr error }{ { name: "nil config", + mode: DefaultConsoleName, wantErr: nil, }, { name: "valid config", + mode: DefaultConsoleName, config: ConsoleConfig{ Level: LevelInfo, }, wantErr: nil, }, { - name: "invalid config", - config: "random things", - wantErr: errors.New("initialize logger: invalid config object: want clog.ConsoleConfig got string"), + name: "custom name", + mode: testName, + wantErr: nil, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.wantErr, New(ModeConsole, 10, tt.config)) + assert.Equal(t, tt.wantErr, NewConsoleWithName(tt.mode, 10, tt.config)) }) } - assert.Equal(t, 1, mgr.len()) - assert.Equal(t, ModeConsole, mgr.loggers[0].Mode()) + assert.Equal(t, 2, mgr.len()) + assert.Equal(t, DefaultConsoleName, mgr.loggers[0].Name()) assert.Equal(t, LevelInfo, mgr.loggers[0].Level()) + assert.Equal(t, testName, mgr.loggers[1].Name()) + assert.Equal(t, LevelTrace, mgr.loggers[1].Level()) } diff --git a/discord.go b/discord.go index 4336c84..7640c58 100644 --- a/discord.go +++ b/discord.go @@ -12,9 +12,6 @@ import ( "time" ) -// ModeDiscord is used to indicate Discord logger. -const ModeDiscord Mode = "discord" - type ( discordEmbed struct { Title string `json:"title"` @@ -67,7 +64,8 @@ type DiscordConfig struct { var _ Logger = (*discordLogger)(nil) type discordLogger struct { - level Level + *noopLogger + url string username string titles []string @@ -76,14 +74,6 @@ type discordLogger struct { client *http.Client } -func (*discordLogger) Mode() Mode { - return ModeDiscord -} - -func (l *discordLogger) Level() Level { - return l.level -} - func (l *discordLogger) buildPayload(m Messager) (string, error) { descPrefixLen := strings.Index(m.String(), "] ") if descPrefixLen == -1 { @@ -163,18 +153,35 @@ func (l *discordLogger) Write(m Messager) error { return fmt.Errorf("gave up after %d retries", retryTimes) } -func init() { - NewRegister(ModeDiscord, func(v interface{}) (Logger, error) { - if v == nil { - v = DiscordConfig{} - } +// DefaultDiscordName is the default name for the Discord logger. +const DefaultDiscordName = "discord" - cfg, ok := v.(DiscordConfig) - if !ok { - return nil, fmt.Errorf("invalid config object: want %T got %T", DiscordConfig{}, v) +// NewDiscord initializes and appends a new Discord logger with default name +// to the managed list. +func NewDiscord(vs ...interface{}) error { + return NewDiscordWithName(DefaultDiscordName, vs...) +} + +// NewDiscordWithName initializes and appends a new Discord logger with given +// name to the managed list. +func NewDiscordWithName(name string, vs ...interface{}) error { + return New(name, DiscordIniter(), vs...) +} + +// DiscordIniter returns the initer for the Discord logger. +func DiscordIniter() Initer { + return func(name string, vs ...interface{}) (Logger, error) { + var cfg *DiscordConfig + for i := range vs { + switch v := vs[i].(type) { + case DiscordConfig: + cfg = &v + } } - if cfg.URL == "" { + if cfg == nil { + return nil, fmt.Errorf("config object with the type '%T' not found", DiscordConfig{}) + } else if cfg.URL == "" { return nil, errors.New("empty URL") } @@ -195,12 +202,14 @@ func init() { } return &discordLogger{ - level: cfg.Level, - url: cfg.URL, + noopLogger: &noopLogger{ + name: name, + level: cfg.Level, + }, username: cfg.Username, titles: titles, colors: colors, client: http.DefaultClient, }, nil - }) + } } diff --git a/discord_test.go b/discord_test.go index 77f0e78..7998063 100644 --- a/discord_test.go +++ b/discord_test.go @@ -11,21 +11,26 @@ import ( "github.com/stretchr/testify/assert" ) -func Test_ModeDiscord(t *testing.T) { - defer Remove(ModeDiscord) +func Test_discordLogger(t *testing.T) { + testName := "Test_ModeDiscord" + defer Remove(DefaultDiscordName) + defer Remove(testName) tests := []struct { name string + mode string config interface{} wantLevel Level wantErr error }{ { name: "nil config", - wantErr: errors.New("initialize logger: empty URL"), + mode: DefaultDiscordName, + wantErr: errors.New("initialize logger: config object with the type 'clog.DiscordConfig' not found"), }, { name: "valid config", + mode: DefaultDiscordName, config: DiscordConfig{ Level: LevelInfo, URL: "https://discordapp.com", @@ -34,10 +39,19 @@ func Test_ModeDiscord(t *testing.T) { }, wantErr: nil, }, + { + name: "custom name", + mode: testName, + config: DiscordConfig{ + URL: "https://discordapp.com", + }, + wantErr: nil, + }, + { name: "invalid config", config: "random things", - wantErr: errors.New("initialize logger: invalid config object: want clog.DiscordConfig got string"), + wantErr: errors.New("initialize logger: config object with the type 'clog.DiscordConfig' not found"), }, { name: "invalid URL", @@ -63,13 +77,15 @@ func Test_ModeDiscord(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.wantErr, New(ModeDiscord, 10, tt.config)) + assert.Equal(t, tt.wantErr, NewDiscordWithName(tt.mode, 10, tt.config)) }) } - assert.Equal(t, 1, mgr.len()) - assert.Equal(t, ModeDiscord, mgr.loggers[0].Mode()) + assert.Equal(t, 2, mgr.len()) + assert.Equal(t, DefaultDiscordName, mgr.loggers[0].Name()) assert.Equal(t, LevelInfo, mgr.loggers[0].Level()) + assert.Equal(t, testName, mgr.loggers[1].Name()) + assert.Equal(t, LevelTrace, mgr.loggers[1].Level()) } func Test_discordLogger_buildPayload(t *testing.T) { diff --git a/file.go b/file.go index b75711f..6ad8bf6 100644 --- a/file.go +++ b/file.go @@ -12,9 +12,6 @@ import ( "time" ) -// ModeFile is used to indicate file logger. -const ModeFile Mode = "file" - const ( simpleDateFormat = "2006-01-02" logPrefixLength = len("2017/02/06 21:20:08 ") @@ -52,7 +49,7 @@ type fileLogger struct { // It is only true when the logger is created by NewFileWriter. standalone bool - level Level + *noopLogger filename string rotationConfig FileRotationConfig @@ -66,14 +63,6 @@ type fileLogger struct { *log.Logger } -func (*fileLogger) Mode() Mode { - return ModeFile -} - -func (l *fileLogger) Level() Level { - return l.level -} - var newLineBytes = []byte("\n") func (l *fileLogger) initFile() (err error) { @@ -234,21 +223,43 @@ func (l *fileLogger) init() error { return nil } -func init() { - NewRegister(ModeFile, func(v interface{}) (Logger, error) { - if v == nil { - v = FileConfig{ - Filename: "clog.log", +// DefaultFileName is the default name for the file logger. +const DefaultFileName = "file" + +// NewFile initializes and appends a new file logger with default name +// to the managed list. +func NewFile(vs ...interface{}) error { + return NewFileWithName(DefaultFileName, vs...) +} + +// NewFileWithName initializes and appends a new file logger with given +// name to the managed list. +func NewFileWithName(name string, vs ...interface{}) error { + return New(name, FileIniter(), vs...) +} + +// FileIniter returns the initer for the file logger. +func FileIniter() Initer { + return func(name string, vs ...interface{}) (Logger, error) { + var cfg *FileConfig + for i := range vs { + switch v := vs[i].(type) { + case FileConfig: + cfg = &v } } - cfg, ok := v.(FileConfig) - if !ok { - return nil, fmt.Errorf("invalid config object: want %T got %T", FileConfig{}, v) + if cfg == nil { + cfg = &FileConfig{ + Filename: "clog.log", + } } l := &fileLogger{ - level: cfg.Level, + noopLogger: &noopLogger{ + name: name, + level: cfg.Level, + }, filename: cfg.Filename, rotationConfig: cfg.FileRotationConfig, } @@ -258,7 +269,7 @@ func init() { } return l, nil - }) + } } var _ io.Writer = (*fileWriter)(nil) diff --git a/file_test.go b/file_test.go index 726dbb9..c838630 100644 --- a/file_test.go +++ b/file_test.go @@ -10,21 +10,26 @@ import ( "github.com/stretchr/testify/assert" ) -func Test_ModeFile(t *testing.T) { - defer Remove(ModeFile) +func Test_fileLogger(t *testing.T) { + testName := "Test_fileLogger" + defer Remove(DefaultFileName) + defer Remove(testName) tests := []struct { name string + mode string config interface{} wantLevel Level wantErr error }{ { name: "nil config", + mode: DefaultFileName, wantErr: nil, }, { name: "valid config", + mode: DefaultFileName, config: FileConfig{ Level: LevelInfo, Filename: filepath.Join(os.TempDir(), "Test_ModeFile"), @@ -32,10 +37,11 @@ func Test_ModeFile(t *testing.T) { wantErr: nil, }, { - name: "invalid config", - config: "random things", - wantErr: errors.New("initialize logger: invalid config object: want clog.FileConfig got string"), + name: "custom name", + mode: testName, + wantErr: nil, }, + { name: "invalid filename", config: FileConfig{ @@ -46,13 +52,15 @@ func Test_ModeFile(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.wantErr, New(ModeFile, 10, tt.config)) + assert.Equal(t, tt.wantErr, NewFileWithName(tt.mode, 10, tt.config)) }) } - assert.Equal(t, 1, mgr.len()) - assert.Equal(t, ModeFile, mgr.loggers[0].Mode()) + assert.Equal(t, 2, mgr.len()) + assert.Equal(t, DefaultFileName, mgr.loggers[0].Name()) assert.Equal(t, LevelInfo, mgr.loggers[0].Level()) + assert.Equal(t, testName, mgr.loggers[1].Name()) + assert.Equal(t, LevelTrace, mgr.loggers[1].Level()) } func Test_rotateFilename(t *testing.T) { diff --git a/go.mod b/go.mod index 47ba7d4..3779a03 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module unknwon.dev/clog/v2 -go 1.12 +go 1.11 require ( github.com/fatih/color v1.7.0 diff --git a/logger.go b/logger.go index ba0d2d2..d57adbe 100644 --- a/logger.go +++ b/logger.go @@ -9,33 +9,31 @@ import ( "github.com/fatih/color" ) -// Logger is an interface for a logger with a specific mode and level. +// Logger is an interface for a logger with a specific name and level. type Logger interface { - // Mode returns the mode of the logger. - Mode() Mode + // Name returns the name can used to identify the logger. + Name() string // Level returns the minimum logging level of the logger. Level() Level // Write processes a Messager entry. Write(Messager) error } -// Register is a factory function taht returns a new logger. -// It accepts a configuration struct specifically for the logger. -type Register func(interface{}) (Logger, error) +var _ Logger = (*noopLogger)(nil) -var registers = map[Mode]Register{} +type noopLogger struct { + name string + level Level +} -// NewRegister adds a new factory function as a Register to the global map. -// -// This function is not concurrent safe. -func NewRegister(mode Mode, r Register) { - if r == nil { - panic("register is nil") - } - if registers[mode] != nil { - panic(fmt.Sprintf("register with mode %q already exists", mode)) +func (l *noopLogger) Name() string { return l.name } +func (l *noopLogger) Level() Level { return l.level } +func (l *noopLogger) Write(_ Messager) error { return nil } + +func noopIniter(name string, _ ...interface{}) Initer { + return func(string, ...interface{}) (Logger, error) { + return &noopLogger{name: name}, nil } - registers[mode] = r } type cancelableLogger struct { @@ -53,11 +51,16 @@ func (l *cancelableLogger) error(err error) { return } - errLogger.Print(errSprintf("[clog] [%s]: %v", l.Mode(), err)) + errLogger.Print(errSprintf("[clog] [%s]: %v", l.Name(), err)) } +const ( + stateStopping int64 = 0 + stateRunning int64 = 1 +) + type manager struct { - state int64 // 0=stopping, 1=running + state int64 ctx context.Context cancel context.CancelFunc loggers []*cancelableLogger @@ -88,7 +91,7 @@ func (m *manager) write(level Level, skip int, format string, v ...interface{}) func (m *manager) stop() { // Make sure cancellation is only propagated once to prevent deadlock of WaitForStop. - if !atomic.CompareAndSwapInt64(&m.state, 1, 0) { + if !atomic.CompareAndSwapInt64(&m.state, stateRunning, stateStopping) { return } @@ -100,36 +103,31 @@ func (m *manager) stop() { var mgr *manager -func initManager() { +func init() { ctx, cancel := context.WithCancel(context.Background()) mgr = &manager{ - state: 1, + state: stateRunning, ctx: ctx, cancel: cancel, } } -func init() { - initManager() -} +// Initer takes a name and arbitrary number of parameters needed for initalization +// and returns an initalized logger. +type Initer func(string, ...interface{}) (Logger, error) // New initializes and appends a new logger to the managed list. // Calling this function multiple times will overwrite previous initialized -// logger with the same mode. +// logger with the same name. // // Any integer type (i.e. int, int32, int64) will be used as buffer size. -// Otherwise, the value will be used as config object to the logger. +// Otherwise, the value will be passed to the initer. // // This function is not concurrent safe. -//func New(mode Mode, bufferSize int64, cfg interface{}) error { -func New(mode Mode, opts ...interface{}) error { - r, ok := registers[mode] - if !ok { - return fmt.Errorf("no register for %q", mode) - } - +func New(name string, initer Initer, opts ...interface{}) error { bufferSize := 0 - var cfg interface{} + + vs := opts[:0] for i := range opts { switch opt := opts[i].(type) { case int: @@ -139,11 +137,11 @@ func New(mode Mode, opts ...interface{}) error { case int64: bufferSize = int(opt) default: - cfg = opt + vs = append(vs, opt) } } - l, err := r(cfg) + l, err := initer(name, vs...) if err != nil { return fmt.Errorf("initialize logger: %v", err) } @@ -163,7 +161,7 @@ func New(mode Mode, opts ...interface{}) error { // Check and replace previous logger found := false for i, l := range mgr.loggers { - if l.Mode() == mode { + if l.Name() == name { found = true // Release previous logger @@ -204,13 +202,13 @@ func New(mode Mode, opts ...interface{}) error { return nil } -// Remove removes a logger with given mode from the managed list. +// Remove removes a logger with given name from the managed list. // // This function is not concurrent safe. -func Remove(mode Mode) { +func Remove(name string) { loggers := mgr.loggers[:0] for _, l := range mgr.loggers { - if l.Mode() == mode { + if l.Name() == name { go func(l *cancelableLogger) { l.cancel() <-l.done diff --git a/logger_test.go b/logger_test.go index e30a72f..5092b9e 100644 --- a/logger_test.go +++ b/logger_test.go @@ -7,155 +7,79 @@ import ( "github.com/stretchr/testify/assert" ) -func TestNewRegister(t *testing.T) { - tests := []struct { - name string - run func() - want string - }{ - { - name: "success", - run: func() { - NewRegister("TestNewRegister_success", - func(_ interface{}) (Logger, error) { return nil, nil }, - ) - }, - want: "", - }, - { - name: "nil register", - run: func() { - NewRegister("", nil) - }, - want: "register is nil", - }, - { - name: "duplicated register", - run: func() { - NewRegister("TestNewRegister_duplicated_register", - func(_ interface{}) (Logger, error) { return nil, nil }, - ) - NewRegister("TestNewRegister_duplicated_register", - func(_ interface{}) (Logger, error) { return nil, nil }, - ) - }, - want: `register with mode "TestNewRegister_duplicated_register" already exists`, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - defer func() { - err := recover() - if tt.want == "" { - assert.Nil(t, err) - } else { - assert.Equal(t, tt.want, err) - } - }() +func TestNew(t *testing.T) { + testGood := "TestNew_good" + testGoodIniter := noopIniter(testGood) - tt.run() - }) + testBad := "TestNew_bad" + testBadIniter := func(string, ...interface{}) (Logger, error) { + return nil, errors.New("random error") } -} - -var _ Logger = (*noopLogger)(nil) - -type noopLogger struct { - mode Mode - level Level -} - -func (l *noopLogger) Mode() Mode { return l.mode } -func (l *noopLogger) Level() Level { return l.level } -func (l *noopLogger) Write(_ Messager) error { return nil } -func TestNew(t *testing.T) { - testModeGood := Mode("TestNew_good") - testModeBad := Mode("TestNew_bad") - defer Remove(testModeGood) - defer Remove(testModeBad) - - NewRegister(testModeGood, - func(_ interface{}) (Logger, error) { - return &noopLogger{ - mode: testModeGood, - }, nil - }, - ) - NewRegister(testModeBad, - func(_ interface{}) (Logger, error) { - return nil, errors.New("random error") - }, - ) + defer Remove(testGood) + defer Remove(testBad) tests := []struct { name string - mode Mode + mode string + initer Initer bufferSize interface{} want error }{ { name: "success", - mode: testModeGood, + mode: testGood, + initer: testGoodIniter, bufferSize: 1, want: nil, }, { name: "success", - mode: testModeGood, + mode: testGood, + initer: testGoodIniter, bufferSize: int32(1), want: nil, }, { name: "success", - mode: testModeGood, + mode: testGood, + initer: testGoodIniter, bufferSize: int64(1), want: nil, }, { - name: "no register", - mode: "no_register", - want: errors.New(`no register for "no_register"`), - }, - { - name: "initialize error", - mode: testModeBad, - want: errors.New("initialize logger: random error"), + name: "initialize error", + mode: testBad, + initer: testBadIniter, + want: errors.New("initialize logger: random error"), }, { - name: "success overwrite", - mode: testModeGood, - want: nil, + name: "success overwrite", + mode: testGood, + initer: testGoodIniter, + want: nil, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := New(tt.mode, tt.bufferSize) + err := New(tt.mode, tt.initer, tt.bufferSize) assert.Equal(t, tt.want, err) }) } } func TestRemove(t *testing.T) { - testMode1 := Mode("TestRemove1") - NewRegister(testMode1, func(_ interface{}) (Logger, error) { - return &noopLogger{ - mode: testMode1, - }, nil - }) - assert.Nil(t, New(testMode1, -1)) + test1 := "TestRemove_1" + test1Initer := noopIniter(test1) + assert.Nil(t, New(test1, test1Initer, -1)) - testMode2 := Mode("TestRemove2") - NewRegister(testMode2, func(_ interface{}) (Logger, error) { - return &noopLogger{ - mode: testMode2, - }, nil - }) - assert.Nil(t, New(testMode2, -1)) + test2 := "TestRemove_2" + test2Initer := noopIniter(test2) + assert.Nil(t, New(test2, test2Initer, -1)) tests := []struct { name string - mode Mode + mode string numLoggers int }{ { @@ -165,12 +89,12 @@ func TestRemove(t *testing.T) { }, { name: "remove one", - mode: testMode1, + mode: test1, numLoggers: 1, }, { name: "remove two", - mode: testMode2, + mode: test2, numLoggers: 0, }, } diff --git a/slack.go b/slack.go index 4e35091..6f38666 100644 --- a/slack.go +++ b/slack.go @@ -10,9 +10,6 @@ import ( "net/http" ) -// ModeSlack is used to indicate Slack logger. -const ModeSlack Mode = "slack" - type slackAttachment struct { Text string `json:"text"` Color string `json:"color"` @@ -44,21 +41,14 @@ type SlackConfig struct { var _ Logger = (*slackLogger)(nil) type slackLogger struct { - level Level + *noopLogger + url string colors []string client *http.Client } -func (*slackLogger) Mode() Mode { - return ModeSlack -} - -func (l *slackLogger) Level() Level { - return l.level -} - func (l *slackLogger) buildPayload(m Messager) (string, error) { payload := slackPayload{ Attachments: []slackAttachment{ @@ -105,18 +95,35 @@ func (l *slackLogger) Write(m Messager) error { return nil } -func init() { - NewRegister(ModeSlack, func(v interface{}) (Logger, error) { - if v == nil { - v = SlackConfig{} - } +// DefaultSlackName is the default name for the Slack logger. +const DefaultSlackName = "slack" - cfg, ok := v.(SlackConfig) - if !ok { - return nil, fmt.Errorf("invalid config object: want %T got %T", SlackConfig{}, v) +// NewSlack initializes and appends a new Slack logger with default name +// to the managed list. +func NewSlack(vs ...interface{}) error { + return NewSlackWithName(DefaultSlackName, vs...) +} + +// NewSlackWithName initializes and appends a new Slack logger with given +// name to the managed list. +func NewSlackWithName(name string, vs ...interface{}) error { + return New(name, SlackIniter(), vs...) +} + +// SlackIniter returns the initer for the Slack logger. +func SlackIniter() Initer { + return func(name string, vs ...interface{}) (Logger, error) { + var cfg *SlackConfig + for i := range vs { + switch v := vs[i].(type) { + case SlackConfig: + cfg = &v + } } - if cfg.URL == "" { + if cfg == nil { + return nil, fmt.Errorf("config object with the type '%T' not found", SlackConfig{}) + } else if cfg.URL == "" { return nil, errors.New("empty URL") } @@ -129,10 +136,13 @@ func init() { } return &slackLogger{ - level: cfg.Level, + noopLogger: &noopLogger{ + name: name, + level: cfg.Level, + }, url: cfg.URL, colors: colors, client: http.DefaultClient, }, nil - }) + } } diff --git a/slack_test.go b/slack_test.go index 87e1734..48b35f7 100644 --- a/slack_test.go +++ b/slack_test.go @@ -10,21 +10,26 @@ import ( "github.com/stretchr/testify/assert" ) -func Test_ModeSlack(t *testing.T) { - defer Remove(ModeSlack) +func Test_slackLogger(t *testing.T) { + testName := "Test_slackLogger" + defer Remove(DefaultSlackName) + defer Remove(testName) tests := []struct { name string + mode string config interface{} wantLevel Level wantErr error }{ { name: "nil config", - wantErr: errors.New("initialize logger: empty URL"), + mode: DefaultSlackName, + wantErr: errors.New("initialize logger: config object with the type 'clog.SlackConfig' not found"), }, { name: "valid config", + mode: DefaultSlackName, config: SlackConfig{ Level: LevelInfo, URL: "https://slack.com", @@ -32,10 +37,19 @@ func Test_ModeSlack(t *testing.T) { }, wantErr: nil, }, + { + name: "custom name", + mode: testName, + config: SlackConfig{ + URL: "https://slack.com", + }, + wantErr: nil, + }, + { name: "invalid config", config: "random things", - wantErr: errors.New("initialize logger: invalid config object: want clog.SlackConfig got string"), + wantErr: errors.New("initialize logger: config object with the type 'clog.SlackConfig' not found"), }, { name: "invalid URL", @@ -53,13 +67,15 @@ func Test_ModeSlack(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.wantErr, New(ModeSlack, 10, tt.config)) + assert.Equal(t, tt.wantErr, NewSlackWithName(tt.mode, 10, tt.config)) }) } - assert.Equal(t, 1, mgr.len()) - assert.Equal(t, ModeSlack, mgr.loggers[0].Mode()) + assert.Equal(t, 2, mgr.len()) + assert.Equal(t, DefaultSlackName, mgr.loggers[0].Name()) assert.Equal(t, LevelInfo, mgr.loggers[0].Level()) + assert.Equal(t, testName, mgr.loggers[1].Name()) + assert.Equal(t, LevelTrace, mgr.loggers[1].Level()) } func Test_slackLogger_buildPayload(t *testing.T) {