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

feat: db connection analytics #13346

Conversation

eschutho
Copy link
Member

SUMMARY

adds analytics logs to the db connection success/failures

TEST PLAN

unit tests

ADDITIONAL INFORMATION

  • Has associated issue:
  • Changes UI
  • Requires DB Migration.
  • Confirm DB Migration upgrade and downgrade tested.
  • Introduces new feature or API
  • Removes existing feature or API

@eschutho eschutho force-pushed the elizabeth/db-connection-analytics branch from e11c306 to eabd614 Compare February 25, 2021 20:31
with closing(engine.raw_connection()) as conn:
if not engine.dialect.do_ping(conn):
raise DBAPIError(None, None, None)
with closing(engine.raw_connection()) as conn:
Copy link
Member Author

Choose a reason for hiding this comment

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

engine wasn't defined if database was None, so I put it under this block.

@codecov
Copy link

codecov bot commented Feb 25, 2021

Codecov Report

❗ No coverage uploaded for pull request base (db-connection-logs@d323eb1). Click here to learn what that means.
The diff coverage is n/a.

Impacted file tree graph

@@                  Coverage Diff                  @@
##             db-connection-logs   #13346   +/-   ##
=====================================================
  Coverage                      ?   54.61%           
=====================================================
  Files                         ?      478           
  Lines                         ?    15982           
  Branches                      ?     4123           
=====================================================
  Hits                          ?     8729           
  Misses                        ?     7253           
  Partials                      ?        0           
Flag Coverage Δ
cypress 54.61% <0.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.


Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d323eb1...0ceb8f7. Read the comment docs.

Copy link
Member

@betodealmeida betodealmeida left a comment

Choose a reason for hiding this comment

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

LGTM, my only concern is that in theory we might fail to rollback a transaction. I also left a comment on using log instead of log_context.

Comment on lines 53 to 58
with event_logger.log_context(action=f"db_connection_failed.{database.db_engine_spec.__name__}"):
db.session.rollback()
Copy link
Member

Choose a reason for hiding this comment

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

This worries me a bit — if there's an error in the context manager we won't rollback the transaction.

I think it would be better to just log, instead of using a context manager:

try:
    event_logger.log(action=f"db_connection_failed.{database.db_engine_spec.__name__}")
finally:
    db.session.rollback()

To ensure that rollback is always called.

Copy link
Member

Choose a reason for hiding this comment

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

Maybe log should be pessimistic / guaranteed to never raise(?) so that we don't have to use caution every time we call it? That would mean wrapping the content of log in a try ?

Copy link
Member Author

Choose a reason for hiding this comment

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

@betodealmeida and I were talking about this earlier, and most statements before the yield are pretty safe, but to be sure, I moved what I could below the yield statement. I don't think there's much risk of this failing now:

 start_time = time.time()
 payload_override = {}

Copy link
Member Author

@eschutho eschutho Feb 26, 2021

Choose a reason for hiding this comment

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

lmk if you think this works as a solution @betodealmeida @mistercrunch

@@ -69,8 +64,9 @@ def run(self) -> Model:
security_manager.add_permission_view_menu("database_access", database.perm)
db.session.commit()
except DAOCreateFailedError as ex:
logger.exception(ex.exception)
raise DatabaseCreateFailedError()
with event_logger.log_context(action=f"db_creation_failed.{ex.exception}"):
Copy link
Member

Choose a reason for hiding this comment

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

We might want to just use log in places like this. The main difference between log and log_context is that the former, in addition to being a context manager, logs the duration automatically, together with some dashboard/chart metadata that is irrelevant here.

Copy link
Member Author

Choose a reason for hiding this comment

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

true... it also logs the user_id and referrer, so maybe it's still useful?

Copy link
Member

Choose a reason for hiding this comment

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

We may want to either 1) refactor log to do some of the magic in log_context maybe add a bool flag enrich_with_request_context=False, or 2) can we just call the context manager with the with block? Would that just work? or 3) refactor log_context to NOT be be context manager and create a new one say log_context_manager (or better name) that's used only when we want duration / with block

Copy link
Member

Choose a reason for hiding this comment

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

One problem is that adding the flag enrich_with_request_context would be a breaking change (our event logger, for example, would have to be updated).

I think the best way here is:

  1. Create a new method in AbstractEventLogger called log_with_context, that does all the job that log_context does but is a function, not a context manager. The method calls log at the end, with all the extracted context.
  2. Change AbstractEventLogger.log_context to use log_with_context for the enrichment, removing the shared code between them.
  3. In this PR, call the new log_with_context. Since it delegates the actual logging to log it will work with all existing event loggers, and is not a breaking change..

Eventually I think we should have the event logger itself be a context manager by implementing __enter__ and __exit__ methods, so we could call:

with event_logger(action="foo"):
   do_something()

Since we can do this by adding methods to AbstractEventLogger it would also not be a breaking change.

Copy link
Member

Choose a reason for hiding this comment

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

Quick clarification that enrich_with_request_context would be false by default to protect backwards compatibity. But maybe function composition is better than a parameter here, I'm ok with with either pattern.

There's a need to really clarify/improve the method in AbstractEventLogger with good/clear names here between functions we can call, content manager(s) and decorator(s)

Copy link
Member Author

Choose a reason for hiding this comment

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

since we're merging this branch into @hughhhh's feature branch rather than master, @betodealmeida said that he could do the log_context refactor in a separate PR.

Copy link
Member

Choose a reason for hiding this comment

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

I'm working on the refactor right now.

@mistercrunch you're right, I forgot that log takes a **kwargs that would swallow the new argument.

@eschutho eschutho changed the title Elizabeth/db connection analytics feature: db connection analytics Feb 25, 2021
@mistercrunch
Copy link
Member

mistercrunch commented Feb 25, 2021

I feel like we may want to add engine as it's own dimension as opposed to overloading the action type with it.

Eventually I'd like to add the engine to all analytics events that interact with analytics databases like viewing a chart, running a SQL Lab query, .

I think you can just pass as a keyword as in event_logger.log_context('test_connection_success', engine=engine)

I made a related comment here: #13223 (comment)

stats_logger.incr(
f"db_connection_failed.{database.db_engine_spec.__name__}"
)
with event_logger.log_context(action=f"db_connection_failed", engine=database.db_engine_spec.__name__):
Copy link
Member Author

@eschutho eschutho Feb 26, 2021

Choose a reason for hiding this comment

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

@mistercrunch I added in the engine here as a kwarg. Also, just as an FYI, the strings that we're passing in these two files aren't exactly 1:1. Here we have to use the engine spec name, and in the test_connection file we pass the engine+driver combo. But the information is essentially the same.. any downstream integrations can still see the engine from the string that is passed in.

@eschutho eschutho changed the title feature: db connection analytics feat: db connection analytics Feb 26, 2021
@eschutho eschutho force-pushed the elizabeth/db-connection-analytics branch 3 times, most recently from f6d8ea1 to 4fe1895 Compare February 26, 2021 18:20
@eschutho
Copy link
Member Author

rerun tests

@eschutho eschutho closed this Feb 26, 2021
@eschutho eschutho reopened this Feb 26, 2021
@eschutho eschutho force-pushed the elizabeth/db-connection-analytics branch 2 times, most recently from fb68366 to d4588f6 Compare February 26, 2021 23:57
@eschutho eschutho force-pushed the elizabeth/db-connection-analytics branch 2 times, most recently from 4538e2f to bf0ff7b Compare March 1, 2021 23:52
@@ -69,8 +68,9 @@ def run(self) -> Model:
security_manager.add_permission_view_menu("database_access", database.perm)
db.session.commit()
except DAOCreateFailedError as ex:
logger.exception(ex.exception)
raise DatabaseCreateFailedError()
with event_logger.log_context(action=f"db_creation_failed.{ex.exception}"):
Copy link
Member Author

Choose a reason for hiding this comment

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

@hughhhh I saw this earlier today, that we're currently logging the entire exception. Can we change this on line 71 and 72 to log just the class? I originally thought since we were already logging it, it would be fine to pass to the event logger, but I think we should actually clean this up as well.

@eschutho
Copy link
Member Author

eschutho commented Mar 6, 2021

@hughhhh is taking this effort over in https://github.com/apache/superset/pull/13468/files

@eschutho eschutho closed this Mar 6, 2021
@eschutho eschutho deleted the elizabeth/db-connection-analytics branch April 16, 2021 23:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants