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

Expose metaflow logger and monitor via singleton #1794

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

talsperre
Copy link

@talsperre talsperre commented Apr 8, 2024

Expose logger and monitor via a system_current singleton

Currently, users don't have the ability to use their implementation of logger and monitor sidecars in their own code. Additionally, if platform developers want to instrument their Metaflow extensions, they have to pass in the logger/monitor constructs to each one of their plugins, leading to code duplication.

This PR exposes two new singleton objects called _system_logger and _system_monitor that can be used to access the monitor and the logger anywhere.

  • The monitor/logger can be accessed both within and outside a Metaflow flow. This allows us to instrument Metaflow plugins like metaflow.S3, which is often used outside of a flow as well.

Usage

The monitor/logger sidecar can be used in the following manner:

with _system_monitor.count("<your_metric_name>"): 
      # your code
      pass

_system_logger.log(payload):

Copy link
Contributor

@romain-intel romain-intel left a comment

Choose a reason for hiding this comment

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

Some initial comments.

metaflow/task.py Show resolved Hide resolved
metaflow/task.py Outdated
pass
system_current.logger.log(
{
"log_type": "ERROR",
Copy link
Contributor

Choose a reason for hiding this comment

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

could we have constants for these?


@contextmanager
def measure(self, metric_name, qualifer_name=None):
timer, counter = self.monitor.get_measure_metrics(metric_name)
Copy link
Contributor

Choose a reason for hiding this comment

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

could we ideally make this look symetric for both?

Copy link
Contributor

Choose a reason for hiding this comment

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

we can also just return a single "payload". Internally, the send_metric would know to stop the timer for example.

Copy link
Contributor

@romain-intel romain-intel left a comment

Choose a reason for hiding this comment

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

A few initial comments but I think this goes in the direction we agreed to so we shouldn't be too far.

@@ -100,6 +100,12 @@ class and related decorators.
# current runtime singleton
from .metaflow_current import current

# system monitor runtime singleton
Copy link
Contributor

Choose a reason for hiding this comment

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

I would add a comment like (for internal metaflow use only). It's kind of explicit from the name but we can be extra explicit :)

metaflow/cli.py Outdated
@@ -1066,6 +1068,10 @@ def start(
if decospecs:
decorators._attach_decorators(ctx.obj.flow, decospecs)

# We create an instance of SystemMonitor and SystemLogger respectively
Copy link
Contributor

Choose a reason for hiding this comment

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

nit, I would move this up to where ctx.obj.monitor and event_logger are created.


@property
def flow_name(self):
return self._flow_name
Copy link
Contributor

Choose a reason for hiding this comment

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

assumes that flow_name will not be called prior to flow which may not be correct. In general, I would have one init method that inits all 4 (flow, flow_name, environment and logger) and controlled by one single flag.

Copy link
Contributor

Choose a reason for hiding this comment

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

This comment is still not addressed I think.

return self._logger

@logger.setter
def logger(self, logger):
Copy link
Contributor

Choose a reason for hiding this comment

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

we don't really want this to be settable independently do we? can we just keep set_logger and have things set directly on the _ names? Or is there a use you are thinking of of setting things independently?

from typing import Dict, Any


class SystemLogger(object):
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: please add type hints where appropriate.

from contextlib import contextmanager


class SystemMonitor(object):
Copy link
Contributor

Choose a reason for hiding this comment

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

same comments as for system_logger

metaflow/task.py Outdated
"ts": round(time.time()),
}
logger.log(msg)
with _system_monitor.count("metaflow.task.start"):
Copy link
Contributor

Choose a reason for hiding this comment

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

I would keep things the same here -- ie: use self.event_logger and self.logger

metaflow/task.py Outdated
"flow_name": self.flow.name,
}
logger.log(tsk_msg)
print("I am here in metaflow")
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: remove.

metaflow/task.py Outdated
"runtime": round(end),
}
logger.log(msg)
_system_monitor.gauge("metaflow.task.duration", duration)
Copy link
Contributor

Choose a reason for hiding this comment

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

A measure would be more appropriate here than a gauge.

Copy link
Author

Choose a reason for hiding this comment

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

Question is should we be measuring the task duration only until the task "ends" or consider the portion dealing with metadata register as part of the task duration as well?

If it's the former then it would be difficult to implement using the existing context manager construct - hence why we use the gauge metric.

Copy link
Contributor

Choose a reason for hiding this comment

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

That's a good point so we may need to add non-context specific ones for this. The issue is that a gauge isn't the same as a measure. Gauge are supposed to measure levels of things (like # of machines in a pool, etc) not disparate point measurements like this.

from typing import Dict, Any, Optional, Union


class SystemLogger(object):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Consider moving this and monitor under metaflow/system so that we can have a rather clean import - from metaflow.system import monitor rather than from .metaflow_system_monitor import _system_monitor or from metaflow import _system_monitor


class SystemLogger(object):
def __init__(self):
self._flow = None
Copy link
Collaborator

Choose a reason for hiding this comment

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

why both _flow and _flow_name?

from typing import Dict, Any, Optional, Union


class SystemLogger(object):
Copy link
Collaborator

Choose a reason for hiding this comment

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

It seems that the implementation of this file depends on its usage outside of the flow context. Can you add comments in the code with the use cases so that it is easier to review and maintain going forward?

self._logger.start()
return self._logger

def set_logger(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Other elements besides logger are being set in this method. consider renaming?

self._logger = LOGGING_SIDECARS[DEFAULT_EVENT_LOGGER](
flow=self.flow, env=self.environment
)
self._debug("Started logger outside of a flow")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Would it be cleaner if self._logger is started when logger is constructed?

"project_flow_name": current.get("project_flow_name"),
"trace_id": trace_id or None,
}
self.event_logger.send(
Copy link
Collaborator

Choose a reason for hiding this comment

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

why use send instead of log? iirc send was a workaround for a very specific use case (I need to dig through the slack conversations) and we would want to avoid introducing it too much in the code base.

metaflow/task.py Show resolved Hide resolved
"traceback": traceback.format_exc(),
}
)
pass
Copy link
Collaborator

Choose a reason for hiding this comment

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

why is this needed?

Copy link
Author

Choose a reason for hiding this comment

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

Replace it with something like event_logger.log(msg="", type="").

"event_value": 1,
}
)
pass
Copy link
Collaborator

Choose a reason for hiding this comment

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

same here - why is a pass needed? is it to signify end of an indent block or something more? if the former, we can avoid introducing pass to maintain consistency of style in the code base

with self.monitor.measure("metaflow.task.duration"):
try:
with self.monitor.count("metaflow.task.start"):
self.event_logger.log(
Copy link
Collaborator

Choose a reason for hiding this comment

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

why is this log necessary?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants