Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix : Incorrect MongoDB Connection Logs #1355

Merged
merged 20 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 58 additions & 11 deletions pkg/gofr/datasource/mongo/mongo.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"net/url"
"time"

"go.opentelemetry.io/otel/attribute"
Expand Down Expand Up @@ -39,7 +40,12 @@ type Config struct {

const defaultTimeout = 5 * time.Second

var errStatusDown = errors.New("status down")
var (
errStatusDown = errors.New("status down")
errMissingField = errors.New("missing required field in config")
errIncorrectURI = errors.New("incorrect URI for mongo")
coolwednesday marked this conversation as resolved.
Show resolved Hide resolved
errParseHost = errors.New("failed to parse host from MongoDB URI")
)

/*
Developer Note: We could have accepted logger and metrics as part of the factory function `New`, but when mongo driver is
Expand Down Expand Up @@ -83,15 +89,14 @@ func (c *Client) UseTracer(tracer any) {

// Connect establishes a connection to MongoDB and registers metrics using the provided configuration when the client was Created.
func (c *Client) Connect() {
c.logger.Debugf("connecting to MongoDB at %v to database %v", c.config.URI, c.config.Database)

uri := c.config.URI

if uri == "" {
uri = fmt.Sprintf("mongodb://%s:%s@%s:%d/%s?authSource=admin",
c.config.User, c.config.Password, c.config.Host, c.config.Port, c.config.Database)
uri, host, err := generateMongoURI(c.config)
if err != nil {
c.logger.Errorf("error generating mongo URI: %v", err)
coolwednesday marked this conversation as resolved.
Show resolved Hide resolved
return
}

c.logger.Debugf("connecting to MongoDB at %v to database %v", c.config.Host, c.config.Database)
coolwednesday marked this conversation as resolved.
Show resolved Hide resolved

timeout := c.config.ConnectionTimeout
if timeout == 0 {
timeout = defaultTimeout
Expand All @@ -108,18 +113,60 @@ func (c *Client) Connect() {
}

if err = m.Ping(ctx, nil); err != nil {
c.logger.Errorf("could not connect to mongoDB at %v due to err: %v", c.config.URI, err)
c.logger.Errorf("could not connect to mongoDB at %v due to err: %v", host, err)
coolwednesday marked this conversation as resolved.
Show resolved Hide resolved
return
}

c.logger.Logf("connected to mongoDB successfully at %v to database %v", c.config.URI, c.config.Database)
c.logger.Logf("connected to mongoDB successfully at %v to database %v", host, c.config.Database)
coolwednesday marked this conversation as resolved.
Show resolved Hide resolved

mongoBuckets := []float64{.05, .075, .1, .125, .15, .2, .3, .5, .75, 1, 2, 3, 4, 5, 7.5, 10}
c.metrics.NewHistogram("app_mongo_stats", "Response time of MONGO queries in milliseconds.", mongoBuckets...)
coolwednesday marked this conversation as resolved.
Show resolved Hide resolved

c.Database = m.Database(c.config.Database)

c.logger.Logf("connected to MongoDB at %v to database %v", uri, c.Database)
c.logger.Logf("connected to MongoDB at %v to database %v", host, c.Database)
}

func generateMongoURI(config *Config) (uri, host string, err error) {
if config.URI != "" {
host, err = getDBHost(config.URI)
if err != nil || host == "" {
return "", "", err
}
coolwednesday marked this conversation as resolved.
Show resolved Hide resolved

return config.URI, host, nil
}

switch {
case config.Host == "":
return "", "", fmt.Errorf("%w: host is empty", errMissingField)
case config.Port == 0:
return "", "", fmt.Errorf("%w: port is empty", errMissingField)
case config.Database == "":
return "", "", fmt.Errorf("%w: database is empty", errMissingField)
}
coolwednesday marked this conversation as resolved.
Show resolved Hide resolved

uri = fmt.Sprintf("mongodb://%s:%s@%s:%d/%s?authSource=admin",
config.User, config.Password, config.Host, config.Port, config.Database)
coolwednesday marked this conversation as resolved.
Show resolved Hide resolved

return uri, config.Host, nil
}

func getDBHost(uri string) (host string, err error) {
parsedURL, err := url.Parse(uri)
coolwednesday marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return "", err
coolwednesday marked this conversation as resolved.
Show resolved Hide resolved
}

if parsedURL.Scheme != "mongodb" {
return "", errIncorrectURI
}

if parsedURL.Hostname() == "" {
return "", errParseHost
}
coolwednesday marked this conversation as resolved.
Show resolved Hide resolved

return parsedURL.Hostname(), nil
}

// InsertOne inserts a single document into the specified collection.
Expand Down
144 changes: 141 additions & 3 deletions pkg/gofr/datasource/mongo/mongo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,155 @@ func Test_NewMongoClient(t *testing.T) {
assert.NotNil(t, client)
}

func TestGenerateMongoURI(t *testing.T) {
tests := []struct {
name string
config Config
expectedURI string
expectedHost string
expectedError string
}{
{
name: "Valid Config",
config: Config{
User: "admin",
Password: "password",
coolwednesday marked this conversation as resolved.
Show resolved Hide resolved
coolwednesday marked this conversation as resolved.
Show resolved Hide resolved
Host: "localhost",
Port: 27017,
Database: "mydb",
},
expectedURI: "mongodb://admin:password@localhost:27017/mydb?authSource=admin",
expectedHost: "localhost",
expectedError: "",
},
{
name: "Predefined URI",
config: Config{
URI: "mongodb://admin:password@localhost:27017/mydb?authSource=admin",
},
expectedURI: "mongodb://admin:password@localhost:27017/mydb?authSource=admin",
expectedHost: "localhost",
expectedError: "",
},
{
name: "Empty Host",
config: Config{
User: "admin",
Password: "password",
Port: 27017,
Database: "mydb",
},
expectedURI: "",
expectedHost: "",
expectedError: "missing required field in config: host is empty",
},
{
name: "Invalid Port",
config: Config{
User: "admin",
Password: "password",
Host: "localhost",
Database: "mydb",
},
expectedURI: "",
expectedHost: "",
expectedError: "missing required field in config: port is empty",
},
{
name: "Empty Database",
config: Config{
User: "admin",
Password: "password",
Host: "localhost",
Port: 27017,
},
expectedURI: "",
expectedHost: "",
expectedError: "missing required field in config: database is empty",
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
client := Client{config: &test.config}
uri, host, err := generateMongoURI(client.config)

assert.Equal(t, test.expectedURI, uri, "Unexpected URI")
assert.Equal(t, test.expectedHost, host, "Unexpected Host")

if test.expectedError != "" {
assert.EqualError(t, err, test.expectedError, "Unexpected error message")
} else {
assert.NoError(t, err, "Expected no error but got one")
}
})
}
}

func TestGetDBHost(t *testing.T) {
tests := []struct {
name string
uri string
expected string
expectedErr string
}{
{
name: "Valid URI with host and port",
uri: "mongodb://username:password@hostname:27017/database?authSource=admin",
expected: "hostname",
expectedErr: "",
},
{
name: "Valid URI with IP address as host",
uri: "mongodb://username:[email protected]:27017/database?authSource=admin",
expected: "192.168.1.1",
expectedErr: "",
},
{
name: "Invalid URI with no host",
uri: "mongodb://username:password@:27017/database?authSource=admin",
expected: "",
expectedErr: "failed to parse host from MongoDB URI",
},
{
name: "Empty URI",
uri: "",
expected: "",
expectedErr: "incorrect URI for mongo",
},
{
name: "Malformed URI",
uri: "mongodb:/username:password@hostname:27017/database?authSource=admin",
expected: "",
expectedErr: "failed to parse host from MongoDB URI",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
host, err := getDBHost(tt.uri)

assert.Equal(t, tt.expected, host, "Test case: %s", tt.name)

if tt.expectedErr == "" {
assert.NoError(t, err, "Test case: %s", tt.name)
} else {
assert.EqualError(t, err, tt.expectedErr, "Test case: %s", tt.name)
}
})
}
}

func Test_NewMongoClientError(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

metrics := NewMockMetrics(ctrl)
logger := NewMockLogger(ctrl)

logger.EXPECT().Debugf("connecting to MongoDB at %v to database %v", "mongo", "test")
logger.EXPECT().Errorf("error while connecting to MongoDB, err:%v", gomock.Any())
logger.EXPECT().Errorf("error generating mongo URI: %v", gomock.Any())

client := New(Config{URI: "mongo", Database: "test"})
client := New(Config{Host: "mongo", Database: "test"})
client.UseLogger(logger)
client.UseMetrics(metrics)
client.Connect()
Expand Down
Loading