diff --git a/cmd/dex/config.go b/cmd/dex/config.go
index dc3715b8f3ca3a2c4db48049845a404af201036a..a2a653a719b10d17db360ae7b08898e971fdca23 100644
--- a/cmd/dex/config.go
+++ b/cmd/dex/config.go
@@ -8,6 +8,7 @@ import (
 
 	"golang.org/x/crypto/bcrypt"
 
+	"github.com/Sirupsen/logrus"
 	"github.com/coreos/dex/connector"
 	"github.com/coreos/dex/connector/github"
 	"github.com/coreos/dex/connector/ldap"
@@ -29,6 +30,7 @@ type Config struct {
 	OAuth2     OAuth2      `json:"oauth2"`
 	GRPC       GRPC        `json:"grpc"`
 	Expiry     Expiry      `json:"expiry"`
+	Logger     Logger      `json:"logger"`
 
 	Frontend server.WebConfig `json:"frontend"`
 
@@ -119,7 +121,7 @@ type Storage struct {
 
 // StorageConfig is a configuration that can create a storage.
 type StorageConfig interface {
-	Open() (storage.Storage, error)
+	Open(logrus.FieldLogger) (storage.Storage, error)
 }
 
 var storages = map[string]func() StorageConfig{
@@ -170,7 +172,7 @@ type Connector struct {
 
 // ConnectorConfig is a configuration that can open a connector.
 type ConnectorConfig interface {
-	Open() (connector.Connector, error)
+	Open(logrus.FieldLogger) (connector.Connector, error)
 }
 
 var connectors = map[string]func() ConnectorConfig{
@@ -223,3 +225,12 @@ type Expiry struct {
 	// IdTokens defines the duration of time for which the IdTokens will be valid.
 	IDTokens string `json:"idTokens"`
 }
+
+// Logger holds configuration required to customize logging for dex.
+type Logger struct {
+	// Level sets logging level severity.
+	Level string `json:"level"`
+
+	// Format specifies the format to be used for logging.
+	Format string `json:"format"`
+}
diff --git a/cmd/dex/config_test.go b/cmd/dex/config_test.go
index f0126f82fd46deda9ba6ae207d70011e3dead284..004fec00c7d3429a8f7de1c9db317e82e5faf13d 100644
--- a/cmd/dex/config_test.go
+++ b/cmd/dex/config_test.go
@@ -59,6 +59,10 @@ staticPasswords:
 expiry:
   signingKeys: "6h"
   idTokens: "24h"
+
+logger:
+  level: "debug"
+  format: "json"
 `)
 
 	want := Config{
@@ -120,6 +124,10 @@ expiry:
 			SigningKeys: "6h",
 			IDTokens:    "24h",
 		},
+		Logger: Logger{
+			Level:  "debug",
+			Format: "json",
+		},
 	}
 
 	var c Config
diff --git a/cmd/dex/serve.go b/cmd/dex/serve.go
index c9baa9d38ce6a0de4ad05812c3404c6eb103c3e1..b47ea9efde1b1981e85091a291104592e32b459f 100644
--- a/cmd/dex/serve.go
+++ b/cmd/dex/serve.go
@@ -9,8 +9,11 @@ import (
 	"log"
 	"net"
 	"net/http"
+	"os"
+	"strings"
 	"time"
 
+	"github.com/Sirupsen/logrus"
 	"github.com/ghodss/yaml"
 	"github.com/spf13/cobra"
 	"golang.org/x/net/context"
@@ -111,6 +114,8 @@ func serve(cmd *cobra.Command, args []string) error {
 		}
 	}
 
+	logger, _ := newLogger(c.Logger.Level, c.Logger.Format)
+
 	connectors := make([]server.Connector, len(c.Connectors))
 	for i, conn := range c.Connectors {
 		if conn.ID == "" {
@@ -119,7 +124,8 @@ func serve(cmd *cobra.Command, args []string) error {
 		if conn.Config == nil {
 			return fmt.Errorf("no config field for connector %q", conn.ID)
 		}
-		c, err := conn.Config.Open()
+		connectorLogger := logger.WithField("connector", conn.Name)
+		c, err := conn.Config.Open(connectorLogger)
 		if err != nil {
 			return fmt.Errorf("open %s: %v", conn.ID, err)
 		}
@@ -130,7 +136,7 @@ func serve(cmd *cobra.Command, args []string) error {
 		}
 	}
 
-	s, err := c.Storage.Config.Open()
+	s, err := c.Storage.Config.Open(logger)
 	if err != nil {
 		return fmt.Errorf("initializing storage: %v", err)
 	}
@@ -153,6 +159,7 @@ func serve(cmd *cobra.Command, args []string) error {
 		Storage:                s,
 		Web:                    c.Frontend,
 		EnablePasswordDB:       c.EnablePasswordDB,
+		Logger:                 logger,
 	}
 	if c.Expiry.SigningKeys != "" {
 		signingKeys, err := time.ParseDuration(c.Expiry.SigningKeys)
@@ -203,3 +210,33 @@ func serve(cmd *cobra.Command, args []string) error {
 
 	return <-errc
 }
+
+func newLogger(level string, format string) (logrus.FieldLogger, error) {
+	var logLevel logrus.Level
+	switch strings.ToLower(level) {
+	case "debug":
+		logLevel = logrus.DebugLevel
+	case "", "info":
+		logLevel = logrus.InfoLevel
+	case "error":
+		logLevel = logrus.ErrorLevel
+	default:
+		return nil, fmt.Errorf("unsupported logLevel: %s", level)
+	}
+
+	var formatter logrus.Formatter
+	switch strings.ToLower(format) {
+	case "", "text":
+		formatter = &logrus.TextFormatter{DisableColors: true}
+	case "json":
+		formatter = &logrus.JSONFormatter{}
+	default:
+		return nil, fmt.Errorf("unsupported logger format: %s", format)
+	}
+
+	return &logrus.Logger{
+		Out:       os.Stderr,
+		Formatter: formatter,
+		Level:     logLevel,
+	}, nil
+}
diff --git a/connector/github/github.go b/connector/github/github.go
index 149883aa34da82bcee74ae66c9225f94658c9482..4ed438522ff9f365e4a452f8bd4ddb4e2bb254c2 100644
--- a/connector/github/github.go
+++ b/connector/github/github.go
@@ -13,6 +13,7 @@ import (
 	"golang.org/x/oauth2"
 	"golang.org/x/oauth2/github"
 
+	"github.com/Sirupsen/logrus"
 	"github.com/coreos/dex/connector"
 )
 
@@ -31,12 +32,13 @@ type Config struct {
 }
 
 // Open returns a strategy for logging in through GitHub.
-func (c *Config) Open() (connector.Connector, error) {
+func (c *Config) Open(logger logrus.FieldLogger) (connector.Connector, error) {
 	return &githubConnector{
 		redirectURI:  c.RedirectURI,
 		org:          c.Org,
 		clientID:     c.ClientID,
 		clientSecret: c.ClientSecret,
+		logger:       logger,
 	}, nil
 }
 
@@ -55,6 +57,7 @@ type githubConnector struct {
 	org          string
 	clientID     string
 	clientSecret string
+	logger       logrus.FieldLogger
 }
 
 func (c *githubConnector) oauth2Config(scopes connector.Scopes) *oauth2.Config {
diff --git a/connector/ldap/ldap.go b/connector/ldap/ldap.go
index d39175c3ccd21ac430cdf8698979399320c6ccae..1af9aab12026e7ec1c39f42984797dd9b4a14f99 100644
--- a/connector/ldap/ldap.go
+++ b/connector/ldap/ldap.go
@@ -13,6 +13,7 @@ import (
 	"golang.org/x/net/context"
 	"gopkg.in/ldap.v2"
 
+	"github.com/Sirupsen/logrus"
 	"github.com/coreos/dex/connector"
 )
 
@@ -135,8 +136,8 @@ func parseScope(s string) (int, bool) {
 }
 
 // Open returns an authentication strategy using LDAP.
-func (c *Config) Open() (connector.Connector, error) {
-	conn, err := c.OpenConnector()
+func (c *Config) Open(logger logrus.FieldLogger) (connector.Connector, error) {
+	conn, err := c.OpenConnector(logger)
 	if err != nil {
 		return nil, err
 	}
@@ -149,7 +150,7 @@ type refreshData struct {
 }
 
 // OpenConnector is the same as Open but returns a type with all implemented connector interfaces.
-func (c *Config) OpenConnector() (interface {
+func (c *Config) OpenConnector(logger logrus.FieldLogger) (interface {
 	connector.Connector
 	connector.PasswordConnector
 	connector.RefreshConnector
@@ -206,7 +207,7 @@ func (c *Config) OpenConnector() (interface {
 	if !ok {
 		return nil, fmt.Errorf("userSearch.Scope unknown value %q", c.GroupSearch.Scope)
 	}
-	return &ldapConnector{*c, userSearchScope, groupSearchScope, tlsConfig}, nil
+	return &ldapConnector{*c, userSearchScope, groupSearchScope, tlsConfig, logger}, nil
 }
 
 type ldapConnector struct {
@@ -216,6 +217,8 @@ type ldapConnector struct {
 	groupSearchScope int
 
 	tlsConfig *tls.Config
+
+	logger logrus.FieldLogger
 }
 
 var (
diff --git a/connector/mock/connectortest.go b/connector/mock/connectortest.go
index 167b0fb28620fc59c841e8414afab31decd77688..b754705b25016768f40d360254819d4e4c69e3e0 100644
--- a/connector/mock/connectortest.go
+++ b/connector/mock/connectortest.go
@@ -9,12 +9,13 @@ import (
 
 	"golang.org/x/net/context"
 
+	"github.com/Sirupsen/logrus"
 	"github.com/coreos/dex/connector"
 )
 
 // NewCallbackConnector returns a mock connector which requires no user interaction. It always returns
 // the same (fake) identity.
-func NewCallbackConnector() connector.Connector {
+func NewCallbackConnector(logger logrus.FieldLogger) connector.Connector {
 	return &Callback{
 		Identity: connector.Identity{
 			UserID:        "0-385-28089-0",
@@ -24,6 +25,7 @@ func NewCallbackConnector() connector.Connector {
 			Groups:        []string{"authors"},
 			ConnectorData: connectorData,
 		},
+		Logger: logger,
 	}
 }
 
@@ -37,6 +39,7 @@ var (
 type Callback struct {
 	// The returned identity.
 	Identity connector.Identity
+	Logger   logrus.FieldLogger
 }
 
 // LoginURL returns the URL to redirect the user to login with.
@@ -67,8 +70,8 @@ func (m *Callback) Refresh(ctx context.Context, s connector.Scopes, identity con
 type CallbackConfig struct{}
 
 // Open returns an authentication strategy which requires no user interaction.
-func (c *CallbackConfig) Open() (connector.Connector, error) {
-	return NewCallbackConnector(), nil
+func (c *CallbackConfig) Open(logger logrus.FieldLogger) (connector.Connector, error) {
+	return NewCallbackConnector(logger), nil
 }
 
 // PasswordConfig holds the configuration for a mock connector which prompts for the supplied
@@ -79,19 +82,20 @@ type PasswordConfig struct {
 }
 
 // Open returns an authentication strategy which prompts for a predefined username and password.
-func (c *PasswordConfig) Open() (connector.Connector, error) {
+func (c *PasswordConfig) Open(logger logrus.FieldLogger) (connector.Connector, error) {
 	if c.Username == "" {
 		return nil, errors.New("no username supplied")
 	}
 	if c.Password == "" {
 		return nil, errors.New("no password supplied")
 	}
-	return &passwordConnector{c.Username, c.Password}, nil
+	return &passwordConnector{c.Username, c.Password, logger}, nil
 }
 
 type passwordConnector struct {
 	username string
 	password string
+	logger   logrus.FieldLogger
 }
 
 func (p passwordConnector) Close() error { return nil }
diff --git a/connector/oidc/oidc.go b/connector/oidc/oidc.go
index 6a71d0173617327637d65b51c99575627260de1c..6a8b6f98689a7e0b564fd25554c6cf97eb4a2c5f 100644
--- a/connector/oidc/oidc.go
+++ b/connector/oidc/oidc.go
@@ -6,6 +6,7 @@ import (
 	"fmt"
 	"net/http"
 
+	"github.com/Sirupsen/logrus"
 	"github.com/coreos/go-oidc"
 	"golang.org/x/net/context"
 	"golang.org/x/oauth2"
@@ -25,7 +26,7 @@ type Config struct {
 
 // Open returns a connector which can be used to login users through an upstream
 // OpenID Connect provider.
-func (c *Config) Open() (conn connector.Connector, err error) {
+func (c *Config) Open(logger logrus.FieldLogger) (conn connector.Connector, err error) {
 	ctx, cancel := context.WithCancel(context.Background())
 
 	provider, err := oidc.NewProvider(ctx, c.Issuer)
@@ -55,6 +56,7 @@ func (c *Config) Open() (conn connector.Connector, err error) {
 			oidc.VerifyExpiry(),
 			oidc.VerifyAudience(clientID),
 		),
+		logger: logger,
 	}, nil
 }
 
@@ -68,6 +70,7 @@ type oidcConnector struct {
 	verifier     *oidc.IDTokenVerifier
 	ctx          context.Context
 	cancel       context.CancelFunc
+	logger       logrus.FieldLogger
 }
 
 func (c *oidcConnector) Close() error {
diff --git a/server/api_test.go b/server/api_test.go
index f6eb7a4d2d31a63847f873f1634de03594f987c2..9f325cbabe2de3d1529694e169b710fbc161dffc 100644
--- a/server/api_test.go
+++ b/server/api_test.go
@@ -2,15 +2,23 @@ package server
 
 import (
 	"context"
+	"os"
 	"testing"
 
+	"github.com/Sirupsen/logrus"
 	"github.com/coreos/dex/api"
 	"github.com/coreos/dex/storage/memory"
 )
 
 // Attempts to create, update and delete a test Password
 func TestPassword(t *testing.T) {
-	s := memory.New()
+	logger := &logrus.Logger{
+		Out:       os.Stderr,
+		Formatter: &logrus.TextFormatter{DisableColors: true},
+		Level:     logrus.DebugLevel,
+	}
+
+	s := memory.New(logger)
 	serv := NewAPI(s)
 
 	ctx := context.Background()
diff --git a/server/server.go b/server/server.go
index 91d119e9fda8b06189eee939bbf3f3265bf9e911..1a87cb9c0e909ed01c01a7ebfa47ce3e6cd9e107 100644
--- a/server/server.go
+++ b/server/server.go
@@ -13,6 +13,7 @@ import (
 	"golang.org/x/crypto/bcrypt"
 	"golang.org/x/net/context"
 
+	"github.com/Sirupsen/logrus"
 	"github.com/gorilla/mux"
 
 	"github.com/coreos/dex/connector"
@@ -57,6 +58,8 @@ type Config struct {
 	EnablePasswordDB bool
 
 	Web WebConfig
+
+	Logger logrus.FieldLogger
 }
 
 // WebConfig holds the server's frontend templates and asset configuration.
@@ -112,6 +115,8 @@ type Server struct {
 	now func() time.Time
 
 	idTokensValidFor time.Duration
+
+	logger logrus.FieldLogger
 }
 
 // NewServer constructs a server from the provided config.
@@ -182,6 +187,7 @@ func newServer(ctx context.Context, c Config, rotationStrategy rotationStrategy)
 		skipApproval:           c.SkipApprovalScreen,
 		now:                    now,
 		templates:              tmpls,
+		logger:                 c.Logger,
 	}
 
 	for _, conn := range c.Connectors {
diff --git a/server/server_test.go b/server/server_test.go
index 3ab41940826a8352990a9ee418959f31a02cc87f..4c9621c9c31824da6bdf6eaa6826f4b2fda3c0e8 100644
--- a/server/server_test.go
+++ b/server/server_test.go
@@ -20,6 +20,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/Sirupsen/logrus"
 	oidc "github.com/coreos/go-oidc"
 	"github.com/kylelemons/godebug/pretty"
 	"golang.org/x/crypto/bcrypt"
@@ -72,19 +73,26 @@ FDWV28nTP9sqbtsmU8Tem2jzMvZ7C/Q0AuDoKELFUpux8shm8wfIhyaPnXUGZoAZ
 Np4vUwMSYV5mopESLWOg3loBxKyLGFtgGKVCjGiQvy6zISQ4fQo=
 -----END RSA PRIVATE KEY-----`)
 
+var logger = &logrus.Logger{
+	Out:       os.Stderr,
+	Formatter: &logrus.TextFormatter{DisableColors: true},
+	Level:     logrus.DebugLevel,
+}
+
 func newTestServer(ctx context.Context, t *testing.T, updateConfig func(c *Config)) (*httptest.Server, *Server) {
 	var server *Server
 	s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		server.ServeHTTP(w, r)
 	}))
+
 	config := Config{
 		Issuer:  s.URL,
-		Storage: memory.New(),
+		Storage: memory.New(logger),
 		Connectors: []Connector{
 			{
 				ID:          "mock",
 				DisplayName: "Mock",
-				Connector:   mock.NewCallbackConnector(),
+				Connector:   mock.NewCallbackConnector(logger),
 			},
 		},
 		Web: WebConfig{
@@ -367,12 +375,17 @@ func TestOAuth2CodeFlow(t *testing.T) {
 			ctx, cancel := context.WithCancel(context.Background())
 			defer cancel()
 
+			logger := &logrus.Logger{
+				Out:       os.Stderr,
+				Formatter: &logrus.TextFormatter{DisableColors: true},
+				Level:     logrus.DebugLevel,
+			}
 			httpServer, s := newTestServer(ctx, t, func(c *Config) {
 				c.Issuer = c.Issuer + "/non-root-path"
 				c.Now = now
 				c.IDTokensValidFor = idTokensValidFor
 				// Create a new mock callback connector for each test case.
-				conn = mock.NewCallbackConnector().(*mock.Callback)
+				conn = mock.NewCallbackConnector(logger).(*mock.Callback)
 				c.Connectors = []Connector{
 					{
 						ID:          "mock",
@@ -743,7 +756,7 @@ func TestCrossClientScopes(t *testing.T) {
 }
 
 func TestPasswordDB(t *testing.T) {
-	s := memory.New()
+	s := memory.New(logger)
 	conn := newPasswordDB(s)
 
 	pw := "hi"
@@ -840,7 +853,7 @@ func TestKeyCacher(t *testing.T) {
 	tNow := time.Now()
 	now := func() time.Time { return tNow }
 
-	s := memory.New()
+	s := memory.New(logger)
 
 	tests := []struct {
 		before            func()
diff --git a/storage/kubernetes/client.go b/storage/kubernetes/client.go
index 55389f1887455a9bbb1cfc0154030e63f57aa41c..b21fb63d20371525afac1438eef477c71e4a0b7d 100644
--- a/storage/kubernetes/client.go
+++ b/storage/kubernetes/client.go
@@ -21,6 +21,7 @@ import (
 	"strings"
 	"time"
 
+	"github.com/Sirupsen/logrus"
 	"github.com/ghodss/yaml"
 	"github.com/gtank/cryptopasta"
 	"golang.org/x/net/context"
@@ -33,6 +34,7 @@ type client struct {
 	client    *http.Client
 	baseURL   string
 	namespace string
+	logger    logrus.FieldLogger
 
 	// Hash function to map IDs (which could span a large range) to Kubernetes names.
 	// While this is not currently upgradable, it could be in the future.
@@ -230,7 +232,7 @@ func (c *client) put(resource, name string, v interface{}) error {
 	return checkHTTPErr(resp, http.StatusOK)
 }
 
-func newClient(cluster k8sapi.Cluster, user k8sapi.AuthInfo, namespace string) (*client, error) {
+func newClient(cluster k8sapi.Cluster, user k8sapi.AuthInfo, namespace string, logger logrus.FieldLogger) (*client, error) {
 	tlsConfig := cryptopasta.DefaultTLSConfig()
 	data := func(b string, file string) ([]byte, error) {
 		if b != "" {
@@ -303,6 +305,7 @@ func newClient(cluster k8sapi.Cluster, user k8sapi.AuthInfo, namespace string) (
 		hash:       func() hash.Hash { return fnv.New64() },
 		namespace:  namespace,
 		apiVersion: "oidc.coreos.com/v1",
+		logger:     logger,
 	}, nil
 }
 
diff --git a/storage/kubernetes/storage.go b/storage/kubernetes/storage.go
index 5471265264d736a0cf84fbe238ac686fff81300b..f3821ee1d64a179dc29acda51515c597b05867b5 100644
--- a/storage/kubernetes/storage.go
+++ b/storage/kubernetes/storage.go
@@ -10,6 +10,7 @@ import (
 
 	"golang.org/x/net/context"
 
+	"github.com/Sirupsen/logrus"
 	"github.com/coreos/dex/storage"
 	"github.com/coreos/dex/storage/kubernetes/k8sapi"
 )
@@ -39,8 +40,8 @@ type Config struct {
 }
 
 // Open returns a storage using Kubernetes third party resource.
-func (c *Config) Open() (storage.Storage, error) {
-	cli, err := c.open()
+func (c *Config) Open(logger logrus.FieldLogger) (storage.Storage, error) {
+	cli, err := c.open(logger)
 	if err != nil {
 		return nil, err
 	}
@@ -48,7 +49,7 @@ func (c *Config) Open() (storage.Storage, error) {
 }
 
 // open returns a client with no garbage collection.
-func (c *Config) open() (*client, error) {
+func (c *Config) open(logger logrus.FieldLogger) (*client, error) {
 	if c.InCluster && (c.KubeConfigFile != "") {
 		return nil, errors.New("cannot specify both 'inCluster' and 'kubeConfigFile'")
 	}
@@ -71,7 +72,7 @@ func (c *Config) open() (*client, error) {
 		return nil, err
 	}
 
-	cli, err := newClient(cluster, user, namespace)
+	cli, err := newClient(cluster, user, namespace, logger)
 	if err != nil {
 		return nil, fmt.Errorf("create client: %v", err)
 	}
diff --git a/storage/kubernetes/storage_test.go b/storage/kubernetes/storage_test.go
index 769bba656d424770a58a0b8c8cbdb51f5163b995..dae641e8d6d357c768aa880de150e14b6d26d0bc 100644
--- a/storage/kubernetes/storage_test.go
+++ b/storage/kubernetes/storage_test.go
@@ -5,6 +5,7 @@ import (
 	"os"
 	"testing"
 
+	"github.com/Sirupsen/logrus"
 	"github.com/coreos/dex/storage"
 	"github.com/coreos/dex/storage/conformance"
 )
@@ -22,7 +23,12 @@ func loadClient(t *testing.T) *client {
 	if config.KubeConfigFile == "" {
 		t.Skipf("test environment variable %q not set, skipping", testKubeConfigEnv)
 	}
-	s, err := config.open()
+	logger := &logrus.Logger{
+		Out:       os.Stderr,
+		Formatter: &logrus.TextFormatter{DisableColors: true},
+		Level:     logrus.DebugLevel,
+	}
+	s, err := config.open(logger)
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/storage/memory/memory.go b/storage/memory/memory.go
index e8f2ce9a7da175239f74cbbe93bf2a20dd5c6506..6d60971753ce9cbb4a046006d004e2e6f0335e42 100644
--- a/storage/memory/memory.go
+++ b/storage/memory/memory.go
@@ -6,17 +6,19 @@ import (
 	"sync"
 	"time"
 
+	"github.com/Sirupsen/logrus"
 	"github.com/coreos/dex/storage"
 )
 
 // New returns an in memory storage.
-func New() storage.Storage {
+func New(logger logrus.FieldLogger) storage.Storage {
 	return &memStorage{
 		clients:       make(map[string]storage.Client),
 		authCodes:     make(map[string]storage.AuthCode),
 		refreshTokens: make(map[string]storage.RefreshToken),
 		authReqs:      make(map[string]storage.AuthRequest),
 		passwords:     make(map[string]storage.Password),
+		logger:        logger,
 	}
 }
 
@@ -28,8 +30,8 @@ type Config struct {
 }
 
 // Open always returns a new in memory storage.
-func (c *Config) Open() (storage.Storage, error) {
-	return New(), nil
+func (c *Config) Open(logger logrus.FieldLogger) (storage.Storage, error) {
+	return New(logger), nil
 }
 
 type memStorage struct {
@@ -42,6 +44,8 @@ type memStorage struct {
 	passwords     map[string]storage.Password
 
 	keys storage.Keys
+
+	logger logrus.FieldLogger
 }
 
 func (s *memStorage) tx(f func()) {
diff --git a/storage/memory/memory_test.go b/storage/memory/memory_test.go
index faa76283c7897e9770e1e72b68cb9eaa4058f2e0..aab63ffc5c474a0a13bb675b58b711f8fa2abc55 100644
--- a/storage/memory/memory_test.go
+++ b/storage/memory/memory_test.go
@@ -1,11 +1,23 @@
 package memory
 
 import (
+	"os"
 	"testing"
 
+	"github.com/Sirupsen/logrus"
+	"github.com/coreos/dex/storage"
 	"github.com/coreos/dex/storage/conformance"
 )
 
 func TestStorage(t *testing.T) {
-	conformance.RunTests(t, New)
+	logger := &logrus.Logger{
+		Out:       os.Stderr,
+		Formatter: &logrus.TextFormatter{DisableColors: true},
+		Level:     logrus.DebugLevel,
+	}
+
+	newStorage := func() storage.Storage {
+		return New(logger)
+	}
+	conformance.RunTests(t, newStorage)
 }
diff --git a/storage/memory/static_clients_test.go b/storage/memory/static_clients_test.go
index f85cad1624a18ccc4dd24fc4f722cc8d14a4bad1..aab8597e45e18c277ef073e0150668e3e736ec01 100644
--- a/storage/memory/static_clients_test.go
+++ b/storage/memory/static_clients_test.go
@@ -1,14 +1,21 @@
 package memory
 
 import (
+	"os"
 	"reflect"
 	"testing"
 
+	"github.com/Sirupsen/logrus"
 	"github.com/coreos/dex/storage"
 )
 
 func TestStaticClients(t *testing.T) {
-	s := New()
+	logger := &logrus.Logger{
+		Out:       os.Stderr,
+		Formatter: &logrus.TextFormatter{DisableColors: true},
+		Level:     logrus.DebugLevel,
+	}
+	s := New(logger)
 
 	c1 := storage.Client{ID: "foo", Secret: "foo_secret"}
 	c2 := storage.Client{ID: "bar", Secret: "bar_secret"}
diff --git a/storage/sql/config.go b/storage/sql/config.go
index 071f0b039997895e71f45e1be2573161125ba4f3..d77e5481311fb796a97b174af41ccbd1ecba6877 100644
--- a/storage/sql/config.go
+++ b/storage/sql/config.go
@@ -6,6 +6,7 @@ import (
 	"net/url"
 	"strconv"
 
+	"github.com/Sirupsen/logrus"
 	"github.com/coreos/dex/storage"
 )
 
@@ -16,15 +17,15 @@ type SQLite3 struct {
 }
 
 // Open creates a new storage implementation backed by SQLite3
-func (s *SQLite3) Open() (storage.Storage, error) {
-	conn, err := s.open()
+func (s *SQLite3) Open(logger logrus.FieldLogger) (storage.Storage, error) {
+	conn, err := s.open(logger)
 	if err != nil {
 		return nil, err
 	}
 	return conn, nil
 }
 
-func (s *SQLite3) open() (*conn, error) {
+func (s *SQLite3) open(logger logrus.FieldLogger) (*conn, error) {
 	db, err := sql.Open("sqlite3", s.File)
 	if err != nil {
 		return nil, err
@@ -34,7 +35,7 @@ func (s *SQLite3) open() (*conn, error) {
 		// doesn't support this, so limit the number of connections to 1.
 		db.SetMaxOpenConns(1)
 	}
-	c := &conn{db, flavorSQLite3}
+	c := &conn{db, flavorSQLite3, logger}
 	if _, err := c.migrate(); err != nil {
 		return nil, fmt.Errorf("failed to perform migrations: %v", err)
 	}
@@ -70,15 +71,15 @@ type Postgres struct {
 }
 
 // Open creates a new storage implementation backed by Postgres.
-func (p *Postgres) Open() (storage.Storage, error) {
-	conn, err := p.open()
+func (p *Postgres) Open(logger logrus.FieldLogger) (storage.Storage, error) {
+	conn, err := p.open(logger)
 	if err != nil {
 		return nil, err
 	}
 	return conn, nil
 }
 
-func (p *Postgres) open() (*conn, error) {
+func (p *Postgres) open(logger logrus.FieldLogger) (*conn, error) {
 	v := url.Values{}
 	set := func(key, val string) {
 		if val != "" {
@@ -113,7 +114,7 @@ func (p *Postgres) open() (*conn, error) {
 	if err != nil {
 		return nil, err
 	}
-	c := &conn{db, flavorPostgres}
+	c := &conn{db, flavorPostgres, logger}
 	if _, err := c.migrate(); err != nil {
 		return nil, fmt.Errorf("failed to perform migrations: %v", err)
 	}
diff --git a/storage/sql/config_test.go b/storage/sql/config_test.go
index 1afcf520d5a3b1a2550227b0bb0782ceb34d5c23..a91195ccfb610456ed3497a5346ae0d6648391b3 100644
--- a/storage/sql/config_test.go
+++ b/storage/sql/config_test.go
@@ -7,6 +7,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/Sirupsen/logrus"
 	"github.com/coreos/dex/storage"
 	"github.com/coreos/dex/storage/conformance"
 )
@@ -41,13 +42,19 @@ func cleanDB(c *conn) error {
 	return err
 }
 
+var logger = &logrus.Logger{
+	Out:       os.Stderr,
+	Formatter: &logrus.TextFormatter{DisableColors: true},
+	Level:     logrus.DebugLevel,
+}
+
 func TestSQLite3(t *testing.T) {
 	newStorage := func() storage.Storage {
 		// NOTE(ericchiang): In memory means we only get one connection at a time. If we
 		// ever write tests that require using multiple connections, for instance to test
 		// transactions, we need to move to a file based system.
 		s := &SQLite3{":memory:"}
-		conn, err := s.open()
+		conn, err := s.open(logger)
 		if err != nil {
 			fmt.Fprintln(os.Stdout, err)
 			t.Fatal(err)
@@ -92,7 +99,7 @@ func TestPostgres(t *testing.T) {
 	}
 
 	newStorage := func() storage.Storage {
-		conn, err := p.open()
+		conn, err := p.open(logger)
 		if err != nil {
 			fatal(err)
 		}
diff --git a/storage/sql/migrate_test.go b/storage/sql/migrate_test.go
index 6c0765a8197c56e60502588c4cf0f430ede2094b..d46839f1800a2ce9be4421b54a261d9ddc01cc25 100644
--- a/storage/sql/migrate_test.go
+++ b/storage/sql/migrate_test.go
@@ -2,7 +2,10 @@ package sql
 
 import (
 	"database/sql"
+	"os"
 	"testing"
+
+	"github.com/Sirupsen/logrus"
 )
 
 func TestMigrate(t *testing.T) {
@@ -12,7 +15,13 @@ func TestMigrate(t *testing.T) {
 	}
 	defer db.Close()
 
-	c := &conn{db, flavorSQLite3}
+	logger := &logrus.Logger{
+		Out:       os.Stderr,
+		Formatter: &logrus.TextFormatter{DisableColors: true},
+		Level:     logrus.DebugLevel,
+	}
+
+	c := &conn{db, flavorSQLite3, logger}
 	for _, want := range []int{len(migrations), 0} {
 		got, err := c.migrate()
 		if err != nil {
diff --git a/storage/sql/sql.go b/storage/sql/sql.go
index 02cc4f4a41945f66f9078dbbf10a1dae0e6cb646..c1f57b0e7f626e59fb0923ee5b61c7b37eb20cd0 100644
--- a/storage/sql/sql.go
+++ b/storage/sql/sql.go
@@ -5,6 +5,7 @@ import (
 	"database/sql"
 	"regexp"
 
+	"github.com/Sirupsen/logrus"
 	"github.com/cockroachdb/cockroach-go/crdb"
 
 	// import third party drivers
@@ -110,6 +111,7 @@ func (f flavor) translate(query string) string {
 type conn struct {
 	db     *sql.DB
 	flavor flavor
+	logger logrus.FieldLogger
 }
 
 func (c *conn) Close() error {