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 14 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
82 changes: 70 additions & 12 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 MongoDB")
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 MongoDB URI: %v", err)
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,71 @@ 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)
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)

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...)
c.metrics.NewHistogram("app_mongo_stats", "Response time of MongoDB queries in milliseconds.", mongoBuckets...)

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 {
return "", "", err
}

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 == "":
coolwednesday marked this conversation as resolved.
Show resolved Hide resolved
return "", "", fmt.Errorf("%w: database is empty", errMissingField)
}
coolwednesday marked this conversation as resolved.
Show resolved Hide resolved

u := &url.URL{
Scheme: "mongodb",
Host: fmt.Sprintf("%s:%d", config.Host, config.Port),
coolwednesday marked this conversation as resolved.
Show resolved Hide resolved
Path: "/" + url.PathEscape(config.Database),
}

if config.User != "" && config.Password != "" {
u.User = url.UserPassword(url.QueryEscape(config.User), url.QueryEscape(config.Password))
}

q := u.Query()
q.Set("authSource", "admin")
u.RawQuery = q.Encode()

return u.String(), u.Hostname(), 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 MongoDB",
},
{
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 MongoDB 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