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

db.DB/redis.Client: Details in Address for GetAddr #53

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

oxzi
Copy link
Member

@oxzi oxzi commented Aug 2, 2024

The idea of a more informative connection information string was brought up in #50. Currently, at least Icinga DB and Icinga Notifications are logging their connection target after startup. This information, however, is quite vague and was now extended to contain, e.g., if TLS is used, the username, the

INFO    icingadb        Connecting to database at 'mysql://icingadb@localhost:3306/icingadb'
INFO    icingadb        Connecting to Redis at 'redis://localhost:6380'

Furthermore, the zapcore.ObjectMarshaler interface was implemented, allowing both *db.DB and *redis.Client to be used as a zap logging field, showing the address.

@oxzi oxzi added the enhancement New feature or request label Aug 2, 2024
@oxzi oxzi requested a review from lippserd August 2, 2024 07:38
@cla-bot cla-bot bot added the cla/signed CLA is signed by all contributors of a PR label Aug 2, 2024
@oxzi oxzi mentioned this pull request Aug 2, 2024
Copy link
Member

@lippserd lippserd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea of informative connection logs, but I would combine that into a new function MustConnect(context.Context, *logging.Logger) for both database.DB and Redis.Client. Ultimately, we do the same thing in many projects, i.e. we log where we connect and fail if it doesn't work. GetAddr() can be removed in this PR (edit: It already is 👍). And instead of "... against database" I would write "... to database".

We could also consider technical strings, e.g. [type[+tls]://]usersname@addr/db: mysql+tls://kubernetes@locahost:3306/kubernetes, user@redis/5.

The idea of a more informative connection information string was brought
up in #50[0]. Currently, at least Icinga DB and Icinga Notifications are
logging their connection target after startup. This information,
however, is quite vague and was now extended to contain, e.g., if TLS is
used, the username, the database.

> INFO    icingadb        Connecting to database at 'mysql://icingadb@localhost:3306/icingadb'
> INFO    icingadb        Connecting to Redis at 'redis://localhost:6380'

Furthermore, the zapcore.ObjectMarshaler interface was implemented,
allowing both *db.DB and *redis.Client to be used as a zap logging
field, showing the address.

[0] #50 (review)
@oxzi oxzi force-pushed the redis-database-human-representation branch from 006d0ec to 8634bd8 Compare August 8, 2024 09:07
@oxzi oxzi changed the title db.DB/redis.Client: Human-readable String Implementation db.DB/redis.Client: Details in Address for GetAddr Aug 8, 2024
@oxzi
Copy link
Member Author

oxzi commented Aug 8, 2024

We could also consider technical strings, e.g. [type[+tls]://]usersname@addr/db: mysql+tls://kubernetes@locahost:3306/kubernetes, user@redis/5.

I actually liked this approach even more. Thus, I have re-introduced the GetAddr method, but now containing all information to produce strings like those. Furthermore, the zapcore.ObjectMarshaler was implemented, allowing the types to be used as zap fields.

Building Icinga DB with this branch results a way more informative output:

INFO    icingadb        Connecting to database at 'mysql://icingadb@localhost:3306/icingadb'
INFO    icingadb        Connecting to Redis at 'redis://localhost:6380'

@oxzi oxzi requested a review from lippserd August 8, 2024 09:10
@oxzi oxzi added this to the 0.3.2 milestone Aug 26, 2024
Database: "db",
User: "user",
},
addr: "mysql://user@/var/empty/mysql.sock/db",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This demonstrates one limitation of the approach: / can appear both in the URL syntax and in a path. The somewhat proper but ugly solution to this would be to just use net/url where this would result in mysql://user@%2Fvar%2Fempty%2Fmysql.sock/db.

A nicer looking alternative might be to use %q instead of %s for unix domain sockets, but that raises the question why make it look like an URL if it actually isn't one.

And a somewhat strange-looking alternative might be to take some inspiration from lib/pq:

// Host and port can alternatively be specified in the query string. lib/pq can't parse the connection URI
// if a Unix domain socket path is specified in the host part of the URI, therefore always use the query
// string. See also https://github.com/lib/pq/issues/796
"host": {c.Host},

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but that raises the question why make it look like an URL if it actually isn't one.

This was inspired by the comment above, #53 (review).

I don't know what exactly would be seen as a beautiful solution. Maybe make it more strange by prefixing a filesystem path with file://. I just checked against the MySQL documentation and found other beauties like root@localhost?socket=%2Ftmp%2Fmysql.sock or root@localhost?socket=(/tmp/mysql.sock).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but that raises the question why make it look like an URL if it actually isn't one.

This was inspired by the comment above, #53 (review).

With my comment I didn't mean to imply that it is absolutely necessary to create URI-like strings, but
since both MySQL and PostgreSQL use URI-like connection strings, I think it's a valid approach to create a relaxed version for logging. For sockets, I would use (/path/to/socket.sock) or just leave it as it is.

default:
return nil, unknownDbType(c.Type)
}

addrDescription := c.Type
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see a benefit renaming addr to addrDescription and introducing typeAddr. I would revert that and do the following here:

if c.TlsOptions.Enable {
    addr = fmt.Sprintf("%s+tls://%s@%s/%s", c.Type, c.User, addr, c.Database)
} else {
    addr = fmt.Sprintf("%s://%s@%s/%s", c.Type, c.User, addr, c.Database)
}

logger: logger,
tableSemaphores: make(map[string]*semaphore.Weighted),
}, nil
}

// GetAddr returns the database host:port or Unix socket address.
// GetAddr returns the Redis connection address in a technical form.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

database not Redis but I would extend it anyway:

GetAddr returns the URI-like database connection string with the following syntax type[+tls]://user@host[:port]/database.

return db.addrDescription
}

// MarshalLogObject implements zapcore.ObjectMarshaler, adding the database address to each log message.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would link to GetAttr here so that it's clear which additional information is logged.

// MarshalLogObject implements zapcore.ObjectMarshaler, adding the database address to each log message.
func (db *DB) MarshalLogObject(encoder zapcore.ObjectEncoder) error {
encoder.AddString("database_address", db.addrDescription)
return nil
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, please add a newline before return as it improves readability.

I know that there is no specific convention or rule in Go regarding the use of newlines before return statements. Even the official Go style guide does not explicitly mention anything in this regard. But we do this in PHP as well and the majority of the code in Icinga DB and Icinga Go library already does this, so I would make this a convention for us.

Database: "db",
User: "user",
},
addr: "mysql://user@/var/empty/mysql.sock/db",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but that raises the question why make it look like an URL if it actually isn't one.

This was inspired by the comment above, #53 (review).

With my comment I didn't mean to imply that it is absolutely necessary to create URI-like strings, but
since both MySQL and PostgreSQL use URI-like connection strings, I think it's a valid approach to create a relaxed version for logging. For sockets, I would use (/path/to/socket.sock) or just leave it as it is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cla/signed CLA is signed by all contributors of a PR enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants