// Copyright 2012 Gary Burd // // 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 logging import ( "bytes" "context" "fmt" "log" "time" "github.com/gomodule/redigo/redis" ) var ( _ redis.ConnWithTimeout = (*loggingConn)(nil) logCallDepth = 3 ) // NewLoggingConn returns a logging wrapper around a connection. func NewLoggingConn(conn redis.Conn, logger *log.Logger, prefix string) redis.Conn { if prefix != "" { prefix = prefix + "." } return &loggingConn{conn, logger, prefix, nil} } // NewLoggingConnFilter returns a logging wrapper around a connection and a filter function. func NewLoggingConnFilter(conn redis.Conn, logger *log.Logger, prefix string, skip func(cmdName string) bool) redis.Conn { if prefix != "" { prefix = prefix + "." } return &loggingConn{conn, logger, prefix, skip} } type loggingConn struct { redis.Conn logger *log.Logger prefix string skip func(cmdName string) bool } func (c *loggingConn) Close() error { err := c.Conn.Close() var buf bytes.Buffer fmt.Fprintf(&buf, "%sClose() -> (%v)", c.prefix, err) c.logger.Output(logCallDepth, buf.String()) // nolint: errcheck return err } func (c *loggingConn) printValue(buf *bytes.Buffer, v interface{}) { const chop = 128 switch v := v.(type) { case []byte: if len(v) > chop { fmt.Fprintf(buf, "%q...", v[:chop]) } else { fmt.Fprintf(buf, "%q", v) } case string: if len(v) > chop { fmt.Fprintf(buf, "%q...", v[:chop]) } else { fmt.Fprintf(buf, "%q", v) } case []interface{}: if len(v) == 0 { buf.WriteString("[]") } else { sep := "[" fin := "]" if len(v) > chop { v = v[:chop] fin = "...]" } for _, vv := range v { buf.WriteString(sep) c.printValue(buf, vv) sep = ", " } buf.WriteString(fin) } default: fmt.Fprint(buf, v) } } func (c *loggingConn) print(method, commandName string, args []interface{}, reply interface{}, err error) { if c.skip != nil && c.skip(commandName) { return } var buf bytes.Buffer fmt.Fprintf(&buf, "%s%s(", c.prefix, method) if method != "Receive" { buf.WriteString(commandName) for _, arg := range args { buf.WriteString(", ") c.printValue(&buf, arg) } } buf.WriteString(") -> (") if method != "Send" { c.printValue(&buf, reply) buf.WriteString(", ") } fmt.Fprintf(&buf, "%v)", err) c.logger.Output(logCallDepth+1, buf.String()) // nolint: errcheck } func (c *loggingConn) Do(commandName string, args ...interface{}) (interface{}, error) { reply, err := c.Conn.Do(commandName, args...) c.print("Do", commandName, args, reply, err) return reply, err } func (c *loggingConn) DoContext(ctx context.Context, commandName string, args ...interface{}) (interface{}, error) { reply, err := redis.DoContext(c.Conn, ctx, commandName, args...) c.print("DoContext", commandName, args, reply, err) return reply, err } func (c *loggingConn) DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (interface{}, error) { reply, err := redis.DoWithTimeout(c.Conn, timeout, commandName, args...) c.print("DoWithTimeout", commandName, args, reply, err) return reply, err } func (c *loggingConn) Send(commandName string, args ...interface{}) error { err := c.Conn.Send(commandName, args...) c.print("Send", commandName, args, nil, err) return err } func (c *loggingConn) Receive() (interface{}, error) { reply, err := c.Conn.Receive() c.print("Receive", "", nil, reply, err) return reply, err } func (c *loggingConn) ReceiveContext(ctx context.Context) (interface{}, error) { reply, err := redis.ReceiveContext(c.Conn, ctx) c.print("ReceiveContext", "", nil, reply, err) return reply, err } func (c *loggingConn) ReceiveWithTimeout(timeout time.Duration) (interface{}, error) { reply, err := redis.ReceiveWithTimeout(c.Conn, timeout) c.print("ReceiveWithTimeout", "", nil, reply, err) return reply, err }