-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathrecording.py
More file actions
131 lines (99 loc) · 3.44 KB
/
recording.py
File metadata and controls
131 lines (99 loc) · 3.44 KB
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
import atexit
import os
from datetime import datetime, timezone
from tempfile import NamedTemporaryFile
from _appmap import generation
from _appmap.web_framework import APPMAP_SUFFIX, HASH_LEN, NAME_MAX, name_hash
from .env import Env
from .recorder import Recorder
logger = Env.current.getLogger(__name__)
class Recording:
"""
Context manager to make it easy to capture a Recording. exit_hook
will be called when the block exits, before any exceptions are
raised.
"""
def __init__(self, exit_hook=None):
self.events = []
self.exit_hook = exit_hook
def start(self):
if not Env.current.enabled:
return
r = Recorder.get_current()
r.clear()
r.start_recording()
def stop(self):
if not Env.current.enabled:
return
self.events += Recorder.stop_recording()
def is_running(self):
if not Env.current.enabled:
return False
return Recorder.get_enabled()
def __enter__(self):
self.start()
def __exit__(self, exc_type, exc_value, tb):
logger.debug("Recording.__exit__, stopping with exception %s", exc_type)
self.stop()
if self.exit_hook is not None:
self.exit_hook(self)
return False
class NoopRecording:
"""
A noop context manager to export as "Recording" instead of class
Recording when not Env.current.enabled.
"""
def __init__(self, exit_hook=None):
self.exit_hook = exit_hook
self.events = []
def start(self):
pass
def stop(self):
pass
def is_running(self):
return False
def __enter__(self):
pass
def __exit__(self, exc_type, exc_value, tb):
if self.exit_hook is not None:
self.exit_hook(self)
return False
def write_appmap(
appmap, appmap_fname, recorder_type, metadata=None, basedir=Env.current.output_dir
):
"""Write an appmap file into basedir.
Adds APPMAP_SUFFIX to basename; shortens the name if necessary.
Atomically replaces existing files. Creates the basedir if required.
"""
if len(appmap_fname) > NAME_MAX - len(APPMAP_SUFFIX):
part = NAME_MAX - len(APPMAP_SUFFIX) - 1 - HASH_LEN
appmap_fname = appmap_fname[:part] + "-" + name_hash(appmap_fname[part:])[:HASH_LEN]
filename = appmap_fname + APPMAP_SUFFIX
basedir = basedir / recorder_type
basedir.mkdir(parents=True, exist_ok=True)
with NamedTemporaryFile(mode="w", dir=basedir, delete=False) as tmp:
tmp.write(generation.dump(appmap, metadata))
appmap_file = basedir / filename
logger.info("writing %s", appmap_file)
os.replace(tmp.name, appmap_file)
def initialize():
if Env.current.enables("process", Env.RECORD_PROCESS_DEFAULT):
r = Recording()
r.start()
def save_at_exit():
nonlocal r
r.stop()
now = datetime.now(timezone.utc)
iso_time = now.isoformat(timespec="seconds").replace("+00:00", "Z")
process_id = os.getpid()
appmap_name = f"{iso_time}-{process_id}".replace(":","-")
recorder_type = "process"
metadata = {
"name": appmap_name,
"recorder": {
"name": "process",
"type": recorder_type,
},
}
write_appmap(r, appmap_name, recorder_type, metadata)
atexit.register(save_at_exit)