-
Notifications
You must be signed in to change notification settings - Fork 37
/
Copy pathlog_operations.py
146 lines (109 loc) · 5.08 KB
/
log_operations.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# Copyright (c) 2018 The Regents of the University of Michigan
# All rights reserved.
# This software is licensed under the BSD 3-Clause License.
"""Builtin execution hook for basic logging."""
import logging
class LogOperations:
""":class:`~.LogOperations` logs information about the execution of operations to a log file.
This hooks provides information, optionally, on the start, successful completion, and/or
erroring of one or more operations in a `flow.FlowProject` instance. The logs are stored in a
file given by the parameter ``fn_logfile``. This file will be appended to if it already exists.
The default formating for the log provides the time, job id, log level, and log message.
.. note::
All logging is performed at the INFO level. To ensure outputs are captured in log files,
use the `--debug` flag when running or submitting jobs, or specify
`submit_options=--debug` in your directives (example shown below).
Examples
--------
The following example will install :class:`~.LogOperations` at the operation level.
Where the log will be stored in a file name `foo.log` in the job workspace.
.. code-block:: python
from flow import FlowProject
from flow.hooks import LogOperations
class Project(FlowProject):
pass
def install_operation_log_hook(operation_name, project_cls):
log = LogOperation(f"{operation_name}.log")
return lambda op: log.install_operation_hooks(op, project_cls)
@install_operation_log_hook("foo", Project)
@Project.operation(directives={
"submit_options": "--debug" # Always submit operation foo with the --debug flag
})
def foo(job):
pass
The code block below provides an example of how install :class:`~.LogOperations` to a
instance of :class:`~.FlowProject`
.. code-block:: python
from flow import FlowProject
from flow.hooks import LogOperations # Import build
class Project(FlowProject):
pass
# Do something
if __name__ == "__main__":
project = Project()
project = LogOperations().install_project_hooks(project)
project.main()
Parameters
----------
fn_logfile: log filename
The name of the log file in the job workspace. Default is "execution-record.log".
"""
def __init__(self, fn_logfile="execution-record.log"):
self._fn_logfile = fn_logfile
# getLogger keep its own cache. This just serves to reduce the time spent setting up loggers
# by only doing it once.
self._loggers = {}
def on_start(self, operation, job):
"""Log the start of execution of a given job(s) operation pair."""
self._get_logger(job).info(f"Starting execution of operation '{operation}'.")
def on_success(self, operation, job):
"""Log the successful completion of a given job(s) operation pair."""
self._get_logger(job).info(
f"Successfully finished execution of operation '{operation}'."
)
def on_exception(self, operation, error, job):
"""Log the raising of an error in the execution of a given job(s) operation pair."""
self._get_logger(job).info(
f"Execution of operation '{operation}' failed with error '{error}'."
)
def _get_logger(self, job):
if job not in self._loggers:
self._loggers[job] = self._setup_logger(job)
return self._loggers[job]
def _setup_logger(self, job):
logger = logging.getLogger(str(job))
fh = logging.FileHandler(job.fn(self._fn_logfile))
fh.setLevel(logging.DEBUG)
formatter = logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
fh.setFormatter(formatter)
logger.addHandler(fh)
return logger
def install_operation_hooks(self, op, project_cls=None):
"""Decorate operation to install log operation to one operation in a signac-flow project.
Parameters
----------
op : function or type
An operation function to log or a subclass of `flow.FlowProject` if ``project_cls`` is
``None``.
project_cls : type
A subclass of `flow.FlowProject`.
"""
if project_cls is None:
return lambda func: self.install_operation_hooks(func, op)
project_cls.operation_hooks.on_start(self.on_start)(op)
project_cls.operation_hooks.on_success(self.on_success)(op)
project_cls.operation_hooks.on_exception(self.on_exception)(op)
return op
def install_project_hooks(self, project):
"""Install log operation to all operations in a signac-flow project.
Parameters
----------
project : flow.FlowProject
The project to install project wide hooks on.
"""
project.project_hooks.on_start.append(self.on_start)
project.project_hooks.on_success.append(self.on_success)
project.project_hooks.on_exception.append(self.on_exception)
return project