Skip to content

Commit

Permalink
feat: logging.StructuredMiddleware (#254)
Browse files Browse the repository at this point in the history
Supports logging with charmbracelet/log in a structured format,
especially useful with JSON logs.
  • Loading branch information
caarlos0 committed Mar 28, 2024
1 parent c8de232 commit 0169ff6
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 13 deletions.
65 changes: 55 additions & 10 deletions logging/logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import (
"github.com/charmbracelet/wish"
)

// Middleware provides basic connection logging. Connects are logged with the
// remote address, invoked command, TERM setting, window dimensions and if the
// auth was public key based. Disconnect will log the remote address and
// connection duration.
// Middleware provides basic connection logging.
// Connects are logged with the remote address, invoked command, TERM setting,
// window dimensions, client version, and if the auth was public key based.
// Disconnect will log the remote address and connection duration.
//
// The logger is set to the std default logger.
// It will use charmbracelet/log.StandardLog() by default.
func Middleware() wish.Middleware {
return MiddlewareWithLogger(log.StandardLog())
}
Expand All @@ -23,25 +23,26 @@ type Logger interface {
Printf(format string, v ...interface{})
}

// MiddlewareWithLogger provides basic connection logging. Connects are logged with the
// remote address, invoked command, TERM setting, window dimensions and if the
// auth was public key based. Disconnect will log the remote address and
// connection duration.
// MiddlewareWithLogger provides basic connection logging.
// Connects are logged with the remote address, invoked command, TERM setting,
// window dimensions, client version, and if the auth was public key based.
// Disconnect will log the remote address and connection duration.
func MiddlewareWithLogger(logger Logger) wish.Middleware {
return func(next ssh.Handler) ssh.Handler {
return func(sess ssh.Session) {
ct := time.Now()
hpk := sess.PublicKey() != nil
pty, _, _ := sess.Pty()
logger.Printf(
"%s connect %s %v %v %s %v %v",
"%s connect %s %v %v %s %v %v %v",
sess.User(),
sess.RemoteAddr().String(),
hpk,
sess.Command(),
pty.Term,
pty.Window.Width,
pty.Window.Height,
sess.Context().ClientVersion(),
)
next(sess)
logger.Printf(
Expand All @@ -52,3 +53,47 @@ func MiddlewareWithLogger(logger Logger) wish.Middleware {
}
}
}

// StructuredMiddleware provides basic connection logging in a structured form.
// Connects are logged with the remote address, invoked command, TERM setting,
// window dimensions, client version, and if the auth was public key based.
// Disconnect will log the remote address and connection duration.
//
// It will use the charmbracelet/log.Default() and Info level by default.
func StructuredMiddleware() wish.Middleware {
return StructuredMiddlewareWithLogger(log.Default(), log.InfoLevel)
}

// StructuredMiddlewareWithLogger provides basic connection logging in a structured form.
// Connects are logged with the remote address, invoked command, TERM setting,
// window dimensions, client version, and if the auth was public key based.
// Disconnect will log the remote address and connection duration.
func StructuredMiddlewareWithLogger(logger *log.Logger, level log.Level) wish.Middleware {
return func(next ssh.Handler) ssh.Handler {
return func(sess ssh.Session) {
ct := time.Now()
hpk := sess.PublicKey() != nil
pty, _, _ := sess.Pty()
logger.Log(
level,
"connect",
"user", sess.User(),
"remote-addr", sess.RemoteAddr().String(),
"public-key", hpk,
"command", sess.Command(),
"term", pty.Term,
"width", pty.Window.Width,
"height", pty.Window.Height,
"client-version", sess.Context().ClientVersion(),
)
next(sess)
logger.Log(
level,
"disconnect",
"user", sess.User(),
"remote-addr", sess.RemoteAddr().String(),
"duration", time.Since(ct),
)
}
}
}
15 changes: 12 additions & 3 deletions logging/logging_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,32 @@ import (
"testing"

"github.com/charmbracelet/ssh"
"github.com/charmbracelet/wish"
"github.com/charmbracelet/wish/logging"
"github.com/charmbracelet/wish/testsession"
gossh "golang.org/x/crypto/ssh"
)

func TestMiddleware(t *testing.T) {
t.Run("inactive term", func(t *testing.T) {
if err := setup(t).Run(""); err != nil {
if err := setup(t, logging.Middleware()).Run(""); err != nil {
t.Error(err)
}
})
}

func setup(tb testing.TB) *gossh.Session {
func TestStructuredMiddleware(t *testing.T) {
t.Run("inactive term", func(t *testing.T) {
if err := setup(t, logging.StructuredMiddleware()).Run(""); err != nil {
t.Error(err)
}
})
}

func setup(tb testing.TB, middleware wish.Middleware) *gossh.Session {
tb.Helper()
return testsession.New(tb, &ssh.Server{
Handler: logging.Middleware()(func(s ssh.Session) {
Handler: middleware(func(s ssh.Session) {
s.Write([]byte("hello"))
}),
}, nil)
Expand Down

0 comments on commit 0169ff6

Please sign in to comment.