meerschaum 3.0.0rc4__py3-none-any.whl → 3.0.0rc8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- meerschaum/_internal/arguments/_parser.py +14 -2
- meerschaum/_internal/cli/__init__.py +6 -0
- meerschaum/_internal/cli/daemons.py +103 -0
- meerschaum/_internal/cli/entry.py +220 -0
- meerschaum/_internal/cli/workers.py +435 -0
- meerschaum/_internal/docs/index.py +1 -2
- meerschaum/_internal/entry.py +44 -8
- meerschaum/_internal/shell/Shell.py +115 -24
- meerschaum/_internal/shell/__init__.py +4 -1
- meerschaum/_internal/static.py +4 -1
- meerschaum/_internal/term/TermPageHandler.py +1 -2
- meerschaum/_internal/term/__init__.py +40 -6
- meerschaum/_internal/term/tools.py +33 -8
- meerschaum/actions/__init__.py +6 -4
- meerschaum/actions/api.py +39 -11
- meerschaum/actions/attach.py +1 -0
- meerschaum/actions/delete.py +4 -2
- meerschaum/actions/edit.py +27 -8
- meerschaum/actions/login.py +8 -8
- meerschaum/actions/register.py +13 -7
- meerschaum/actions/reload.py +22 -5
- meerschaum/actions/restart.py +14 -0
- meerschaum/actions/show.py +69 -4
- meerschaum/actions/start.py +135 -14
- meerschaum/actions/stop.py +36 -3
- meerschaum/actions/sync.py +6 -1
- meerschaum/api/__init__.py +35 -13
- meerschaum/api/_events.py +2 -2
- meerschaum/api/_oauth2.py +47 -4
- meerschaum/api/dash/callbacks/dashboard.py +29 -0
- meerschaum/api/dash/callbacks/jobs.py +3 -2
- meerschaum/api/dash/callbacks/login.py +10 -1
- meerschaum/api/dash/callbacks/register.py +9 -2
- meerschaum/api/dash/pages/login.py +2 -2
- meerschaum/api/dash/pipes.py +72 -36
- meerschaum/api/dash/webterm.py +14 -6
- meerschaum/api/models/_pipes.py +7 -1
- meerschaum/api/resources/static/js/terminado.js +3 -0
- meerschaum/api/resources/static/js/xterm-addon-unicode11.js +2 -0
- meerschaum/api/resources/templates/termpage.html +1 -0
- meerschaum/api/routes/_jobs.py +23 -11
- meerschaum/api/routes/_login.py +73 -5
- meerschaum/api/routes/_pipes.py +6 -4
- meerschaum/api/routes/_webterm.py +3 -3
- meerschaum/config/__init__.py +60 -13
- meerschaum/config/_default.py +89 -61
- meerschaum/config/_edit.py +10 -8
- meerschaum/config/_formatting.py +2 -0
- meerschaum/config/_patch.py +4 -2
- meerschaum/config/_paths.py +127 -12
- meerschaum/config/_read_config.py +32 -12
- meerschaum/config/_version.py +1 -1
- meerschaum/config/environment.py +262 -0
- meerschaum/config/stack/__init__.py +7 -5
- meerschaum/connectors/_Connector.py +1 -2
- meerschaum/connectors/__init__.py +37 -2
- meerschaum/connectors/api/_APIConnector.py +1 -1
- meerschaum/connectors/api/_jobs.py +11 -0
- meerschaum/connectors/api/_pipes.py +7 -1
- meerschaum/connectors/instance/_plugins.py +9 -1
- meerschaum/connectors/instance/_tokens.py +20 -3
- meerschaum/connectors/instance/_users.py +8 -1
- meerschaum/connectors/parse.py +1 -1
- meerschaum/connectors/sql/_create_engine.py +3 -0
- meerschaum/connectors/sql/_pipes.py +93 -79
- meerschaum/connectors/sql/_users.py +8 -1
- meerschaum/connectors/valkey/_ValkeyConnector.py +3 -3
- meerschaum/connectors/valkey/_pipes.py +7 -5
- meerschaum/core/Pipe/__init__.py +45 -71
- meerschaum/core/Pipe/_attributes.py +66 -90
- meerschaum/core/Pipe/_cache.py +555 -0
- meerschaum/core/Pipe/_clear.py +0 -11
- meerschaum/core/Pipe/_data.py +0 -50
- meerschaum/core/Pipe/_deduplicate.py +0 -13
- meerschaum/core/Pipe/_delete.py +12 -21
- meerschaum/core/Pipe/_drop.py +11 -23
- meerschaum/core/Pipe/_dtypes.py +1 -1
- meerschaum/core/Pipe/_index.py +8 -14
- meerschaum/core/Pipe/_sync.py +12 -18
- meerschaum/core/Plugin/_Plugin.py +7 -1
- meerschaum/core/Token/_Token.py +1 -1
- meerschaum/core/User/_User.py +1 -2
- meerschaum/jobs/_Executor.py +88 -4
- meerschaum/jobs/_Job.py +146 -36
- meerschaum/jobs/systemd.py +7 -2
- meerschaum/plugins/__init__.py +277 -81
- meerschaum/utils/daemon/Daemon.py +197 -42
- meerschaum/utils/daemon/FileDescriptorInterceptor.py +0 -1
- meerschaum/utils/daemon/RotatingFile.py +63 -36
- meerschaum/utils/daemon/StdinFile.py +53 -13
- meerschaum/utils/daemon/__init__.py +18 -5
- meerschaum/utils/daemon/_names.py +6 -3
- meerschaum/utils/debug.py +34 -4
- meerschaum/utils/dtypes/__init__.py +5 -1
- meerschaum/utils/formatting/__init__.py +4 -1
- meerschaum/utils/formatting/_jobs.py +1 -1
- meerschaum/utils/formatting/_pipes.py +47 -46
- meerschaum/utils/formatting/_shell.py +33 -9
- meerschaum/utils/misc.py +22 -38
- meerschaum/utils/packages/__init__.py +15 -13
- meerschaum/utils/packages/_packages.py +1 -0
- meerschaum/utils/pipes.py +33 -5
- meerschaum/utils/process.py +1 -1
- meerschaum/utils/prompt.py +172 -143
- meerschaum/utils/sql.py +12 -2
- meerschaum/utils/threading.py +42 -0
- meerschaum/utils/venv/__init__.py +2 -0
- meerschaum/utils/warnings.py +19 -13
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/METADATA +3 -1
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/RECORD +116 -110
- meerschaum/config/_environment.py +0 -145
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/WHEEL +0 -0
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/entry_points.txt +0 -0
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/licenses/LICENSE +0 -0
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/licenses/NOTICE +0 -0
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/top_level.txt +0 -0
- {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/zip-safe +0 -0
@@ -25,9 +25,6 @@ from meerschaum.utils.typing import (
|
|
25
25
|
)
|
26
26
|
from meerschaum.config import get_config
|
27
27
|
from meerschaum._internal.static import STATIC_CONFIG
|
28
|
-
from meerschaum.config._paths import (
|
29
|
-
DAEMON_RESOURCES_PATH, LOGS_RESOURCES_PATH, DAEMON_ERROR_LOG_PATH,
|
30
|
-
)
|
31
28
|
from meerschaum.config._patch import apply_patch_to_config
|
32
29
|
from meerschaum.utils.warnings import warn, error
|
33
30
|
from meerschaum.utils.packages import attempt_import
|
@@ -144,6 +141,7 @@ class Daemon:
|
|
144
141
|
daemon_id: Optional[str] = None,
|
145
142
|
label: Optional[str] = None,
|
146
143
|
properties: Optional[Dict[str, Any]] = None,
|
144
|
+
pickle: bool = True,
|
147
145
|
):
|
148
146
|
"""
|
149
147
|
Parameters
|
@@ -211,6 +209,8 @@ class Daemon:
|
|
211
209
|
error("Cannot create a Daemon without a target.")
|
212
210
|
self.target = target
|
213
211
|
|
212
|
+
self.pickle = pickle
|
213
|
+
|
214
214
|
### NOTE: We have to check self.__dict__ in case we un-pickling.
|
215
215
|
if '_target_args' not in self.__dict__:
|
216
216
|
self._target_args = target_args
|
@@ -224,10 +224,17 @@ class Daemon:
|
|
224
224
|
else str(self.target)
|
225
225
|
)
|
226
226
|
self.label = label
|
227
|
+
elif label is not None:
|
228
|
+
self.label = label
|
229
|
+
|
227
230
|
if 'daemon_id' not in self.__dict__:
|
228
231
|
self.daemon_id = get_new_daemon_name()
|
229
232
|
if '_properties' not in self.__dict__:
|
230
233
|
self._properties = properties
|
234
|
+
elif properties:
|
235
|
+
if self._properties is None:
|
236
|
+
self._properties = {}
|
237
|
+
self._properties.update(properties)
|
231
238
|
if self._properties is None:
|
232
239
|
self._properties = {}
|
233
240
|
|
@@ -276,6 +283,24 @@ class Daemon:
|
|
276
283
|
|
277
284
|
self._setup(allow_dirty_run)
|
278
285
|
|
286
|
+
_daemons.append(self)
|
287
|
+
|
288
|
+
logs_cf = self.properties.get('logs', {})
|
289
|
+
log_refresh_seconds = logs_cf.get('refresh_files_seconds', None)
|
290
|
+
if log_refresh_seconds is None:
|
291
|
+
log_refresh_seconds = get_config('jobs', 'logs', 'refresh_files_seconds')
|
292
|
+
write_timestamps = logs_cf.get('write_timestamps', None)
|
293
|
+
if write_timestamps is None:
|
294
|
+
write_timestamps = get_config('jobs', 'logs', 'timestamps', 'enabled')
|
295
|
+
|
296
|
+
self._log_refresh_timer = RepeatTimer(
|
297
|
+
log_refresh_seconds,
|
298
|
+
partial(self.rotating_log.refresh_files, start_interception=write_timestamps),
|
299
|
+
)
|
300
|
+
|
301
|
+
capture_stdin = logs_cf.get('stdin', True)
|
302
|
+
cwd = self.properties.get('cwd', os.getcwd())
|
303
|
+
|
279
304
|
### NOTE: The SIGINT handler has been removed so that child processes may handle
|
280
305
|
### KeyboardInterrupts themselves.
|
281
306
|
### The previous aggressive approach was redundant because of the SIGTERM handler.
|
@@ -283,7 +308,8 @@ class Daemon:
|
|
283
308
|
pidfile=self.pid_lock,
|
284
309
|
stdout=self.rotating_log,
|
285
310
|
stderr=self.rotating_log,
|
286
|
-
|
311
|
+
stdin=(self.stdin_file if capture_stdin else None),
|
312
|
+
working_directory=cwd,
|
287
313
|
detach_process=True,
|
288
314
|
files_preserve=list(self.rotating_log.subfile_objects.values()),
|
289
315
|
signal_map={
|
@@ -291,18 +317,14 @@ class Daemon:
|
|
291
317
|
},
|
292
318
|
)
|
293
319
|
|
294
|
-
|
295
|
-
|
296
|
-
log_refresh_seconds = get_config('jobs', 'logs', 'refresh_files_seconds')
|
297
|
-
self._log_refresh_timer = RepeatTimer(
|
298
|
-
log_refresh_seconds,
|
299
|
-
partial(self.rotating_log.refresh_files, start_interception=True),
|
300
|
-
)
|
320
|
+
if capture_stdin and sys.stdin is None:
|
321
|
+
raise OSError("Cannot daemonize without stdin.")
|
301
322
|
|
302
323
|
try:
|
303
324
|
os.environ['LINES'], os.environ['COLUMNS'] = str(int(lines)), str(int(columns))
|
304
325
|
with self._daemon_context:
|
305
|
-
|
326
|
+
if capture_stdin:
|
327
|
+
sys.stdin = self.stdin_file
|
306
328
|
_ = os.environ.pop(STATIC_CONFIG['environment']['systemd_stdin_path'], None)
|
307
329
|
os.environ[STATIC_CONFIG['environment']['daemon_id']] = self.daemon_id
|
308
330
|
os.environ['PYTHONUNBUFFERED'] = '1'
|
@@ -360,8 +382,15 @@ class Daemon:
|
|
360
382
|
|
361
383
|
except Exception:
|
362
384
|
daemon_error = traceback.format_exc()
|
385
|
+
from meerschaum.config.paths import DAEMON_ERROR_LOG_PATH
|
363
386
|
with open(DAEMON_ERROR_LOG_PATH, 'a+', encoding='utf-8') as f:
|
364
|
-
f.write(
|
387
|
+
f.write(
|
388
|
+
f"Error in Daemon '{self}':\n\n"
|
389
|
+
f"{sys.stdin=}\n"
|
390
|
+
f"{self.stdin_file_path=}\n"
|
391
|
+
f"{self.stdin_file_path.exists()=}\n\n"
|
392
|
+
f"{daemon_error}\n\n"
|
393
|
+
)
|
365
394
|
warn(f"Encountered an error while running the daemon '{self}':\n{daemon_error}")
|
366
395
|
|
367
396
|
def _capture_process_timestamp(
|
@@ -396,6 +425,8 @@ class Daemon:
|
|
396
425
|
self,
|
397
426
|
keep_daemon_output: bool = True,
|
398
427
|
allow_dirty_run: bool = False,
|
428
|
+
wait: bool = False,
|
429
|
+
timeout: Union[int, float] = 4,
|
399
430
|
debug: bool = False,
|
400
431
|
) -> SuccessTuple:
|
401
432
|
"""Run the daemon as a child process and continue executing the parent.
|
@@ -410,6 +441,12 @@ class Daemon:
|
|
410
441
|
This option is dangerous because if the same `daemon_id` runs concurrently,
|
411
442
|
the last to finish will overwrite the output of the first.
|
412
443
|
|
444
|
+
wait: bool, default True
|
445
|
+
If `True`, block until `Daemon.status` is running (or the timeout expires).
|
446
|
+
|
447
|
+
timeout: Union[int, float], default 4
|
448
|
+
If `wait` is `True`, block for up to `timeout` seconds before returning a failure.
|
449
|
+
|
413
450
|
Returns
|
414
451
|
-------
|
415
452
|
A SuccessTuple indicating success.
|
@@ -433,20 +470,44 @@ class Daemon:
|
|
433
470
|
return _write_pickle_success_tuple
|
434
471
|
|
435
472
|
_launch_daemon_code = (
|
436
|
-
"from meerschaum.utils.daemon import Daemon; "
|
437
|
-
|
438
|
-
|
439
|
-
|
473
|
+
"from meerschaum.utils.daemon import Daemon, _daemons; "
|
474
|
+
f"daemon = Daemon(daemon_id='{self.daemon_id}'); "
|
475
|
+
f"_daemons['{self.daemon_id}'] = daemon; "
|
476
|
+
f"daemon._run_exit(keep_daemon_output={keep_daemon_output}, "
|
477
|
+
"allow_dirty_run=True)"
|
440
478
|
)
|
441
479
|
env = dict(os.environ)
|
442
|
-
env[STATIC_CONFIG['environment']['noninteractive']] = 'true'
|
443
480
|
_launch_success_bool = venv_exec(_launch_daemon_code, debug=debug, venv=None, env=env)
|
444
481
|
msg = (
|
445
482
|
"Success"
|
446
483
|
if _launch_success_bool
|
447
484
|
else f"Failed to start daemon '{self.daemon_id}'."
|
448
485
|
)
|
449
|
-
|
486
|
+
if not wait or not _launch_success_bool:
|
487
|
+
return _launch_success_bool, msg
|
488
|
+
|
489
|
+
timeout = self.get_timeout_seconds(timeout)
|
490
|
+
check_timeout_interval = self.get_check_timeout_interval_seconds()
|
491
|
+
|
492
|
+
if not timeout:
|
493
|
+
success = self.status == 'running'
|
494
|
+
msg = "Success" if success else f"Failed to run daemon '{self.daemon_id}'."
|
495
|
+
if success:
|
496
|
+
self._capture_process_timestamp('began')
|
497
|
+
return success, msg
|
498
|
+
|
499
|
+
begin = time.perf_counter()
|
500
|
+
while (time.perf_counter() - begin) < timeout:
|
501
|
+
if self.status == 'running':
|
502
|
+
self._capture_process_timestamp('began')
|
503
|
+
return True, "Success"
|
504
|
+
time.sleep(check_timeout_interval)
|
505
|
+
|
506
|
+
return False, (
|
507
|
+
f"Failed to start daemon '{self.daemon_id}' within {timeout} second"
|
508
|
+
+ ('s' if timeout != 1 else '') + '.'
|
509
|
+
)
|
510
|
+
|
450
511
|
|
451
512
|
def kill(self, timeout: Union[int, float, None] = 8) -> SuccessTuple:
|
452
513
|
"""
|
@@ -466,10 +527,14 @@ class Daemon:
|
|
466
527
|
success, msg = self._send_signal(signal.SIGTERM, timeout=timeout)
|
467
528
|
if success:
|
468
529
|
self._write_stop_file('kill')
|
530
|
+
self.stdin_file.close()
|
531
|
+
self._remove_blocking_stdin_file()
|
469
532
|
return success, msg
|
470
533
|
|
471
534
|
if self.status == 'stopped':
|
472
535
|
self._write_stop_file('kill')
|
536
|
+
self.stdin_file.close()
|
537
|
+
self._remove_blocking_stdin_file()
|
473
538
|
return True, "Process has already stopped."
|
474
539
|
|
475
540
|
psutil = attempt_import('psutil')
|
@@ -494,6 +559,8 @@ class Daemon:
|
|
494
559
|
pass
|
495
560
|
|
496
561
|
self._write_stop_file('kill')
|
562
|
+
self.stdin_file.close()
|
563
|
+
self._remove_blocking_stdin_file()
|
497
564
|
return True, "Success"
|
498
565
|
|
499
566
|
def quit(self, timeout: Union[int, float, None] = None) -> SuccessTuple:
|
@@ -504,6 +571,8 @@ class Daemon:
|
|
504
571
|
signal_success, signal_msg = self._send_signal(signal.SIGINT, timeout=timeout)
|
505
572
|
if signal_success:
|
506
573
|
self._write_stop_file('quit')
|
574
|
+
self.stdin_file.close()
|
575
|
+
self._remove_blocking_stdin_file()
|
507
576
|
return signal_success, signal_msg
|
508
577
|
|
509
578
|
def pause(
|
@@ -526,6 +595,8 @@ class Daemon:
|
|
526
595
|
-------
|
527
596
|
A `SuccessTuple` indicating whether the `Daemon` process was successfully suspended.
|
528
597
|
"""
|
598
|
+
self._remove_blocking_stdin_file()
|
599
|
+
|
529
600
|
if self.process is None:
|
530
601
|
return False, f"Daemon '{self.daemon_id}' is not running and cannot be paused."
|
531
602
|
|
@@ -533,6 +604,8 @@ class Daemon:
|
|
533
604
|
return True, f"Daemon '{self.daemon_id}' is already paused."
|
534
605
|
|
535
606
|
self._write_stop_file('pause')
|
607
|
+
self.stdin_file.close()
|
608
|
+
self._remove_blocking_stdin_file()
|
536
609
|
try:
|
537
610
|
self.process.suspend()
|
538
611
|
except Exception as e:
|
@@ -598,6 +671,9 @@ class Daemon:
|
|
598
671
|
|
599
672
|
self._remove_stop_file()
|
600
673
|
try:
|
674
|
+
if self.process is None:
|
675
|
+
return False, f"Cannot resume daemon '{self.daemon_id}'."
|
676
|
+
|
601
677
|
self.process.resume()
|
602
678
|
except Exception as e:
|
603
679
|
return False, f"Failed to resume daemon '{self.daemon_id}':\n{e}"
|
@@ -671,6 +747,18 @@ class Daemon:
|
|
671
747
|
except Exception:
|
672
748
|
return {}
|
673
749
|
|
750
|
+
def _remove_blocking_stdin_file(self) -> mrsm.SuccessTuple:
|
751
|
+
"""
|
752
|
+
Remove the blocking STDIN file if it exists.
|
753
|
+
"""
|
754
|
+
try:
|
755
|
+
if self.blocking_stdin_file_path.exists():
|
756
|
+
self.blocking_stdin_file_path.unlink()
|
757
|
+
except Exception as e:
|
758
|
+
return False, str(e)
|
759
|
+
|
760
|
+
return True, "Success"
|
761
|
+
|
674
762
|
def _handle_sigterm(self, signal_number: int, stack_frame: 'frame') -> None:
|
675
763
|
"""
|
676
764
|
Handle `SIGTERM` within the `Daemon` context.
|
@@ -799,7 +887,7 @@ class Daemon:
|
|
799
887
|
if self.process is None:
|
800
888
|
return 'stopped'
|
801
889
|
|
802
|
-
psutil = attempt_import('psutil')
|
890
|
+
psutil = attempt_import('psutil', lazy=False)
|
803
891
|
try:
|
804
892
|
if self.process.status() == 'stopped':
|
805
893
|
return 'paused'
|
@@ -820,6 +908,7 @@ class Daemon:
|
|
820
908
|
"""
|
821
909
|
Return a Daemon's path from its `daemon_id`.
|
822
910
|
"""
|
911
|
+
from meerschaum.config.paths import DAEMON_RESOURCES_PATH
|
823
912
|
return DAEMON_RESOURCES_PATH / daemon_id
|
824
913
|
|
825
914
|
@property
|
@@ -855,7 +944,12 @@ class Daemon:
|
|
855
944
|
"""
|
856
945
|
Return the log path.
|
857
946
|
"""
|
858
|
-
|
947
|
+
logs_cf = self.properties.get('logs', None) or {}
|
948
|
+
if 'path' not in logs_cf:
|
949
|
+
from meerschaum.config.paths import LOGS_RESOURCES_PATH
|
950
|
+
return LOGS_RESOURCES_PATH / (self.daemon_id + '.log')
|
951
|
+
|
952
|
+
return pathlib.Path(logs_cf['path'])
|
859
953
|
|
860
954
|
@property
|
861
955
|
def stdin_file_path(self) -> pathlib.Path:
|
@@ -874,13 +968,33 @@ class Daemon:
|
|
874
968
|
|
875
969
|
return self.path / 'input.stdin.block'
|
876
970
|
|
971
|
+
@property
|
972
|
+
def prompt_kwargs_file_path(self) -> pathlib.Path:
|
973
|
+
"""
|
974
|
+
Return the file path to the kwargs for the invoking `prompt()`.
|
975
|
+
"""
|
976
|
+
return self.path / 'prompt_kwargs.json'
|
977
|
+
|
877
978
|
@property
|
878
979
|
def log_offset_path(self) -> pathlib.Path:
|
879
980
|
"""
|
880
981
|
Return the log offset file path.
|
881
982
|
"""
|
983
|
+
from meerschaum.config.paths import LOGS_RESOURCES_PATH
|
882
984
|
return LOGS_RESOURCES_PATH / ('.' + self.daemon_id + '.log.offset')
|
883
985
|
|
986
|
+
@property
|
987
|
+
def log_offset_lock(self) -> 'fasteners.InterProcessLock':
|
988
|
+
"""
|
989
|
+
Return the process lock context manager.
|
990
|
+
"""
|
991
|
+
if '_log_offset_lock' in self.__dict__:
|
992
|
+
return self._log_offset_lock
|
993
|
+
|
994
|
+
fasteners = attempt_import('fasteners')
|
995
|
+
self._log_offset_lock = fasteners.InterProcessLock(self.log_offset_path)
|
996
|
+
return self._log_offset_lock
|
997
|
+
|
884
998
|
@property
|
885
999
|
def rotating_log(self) -> RotatingFile:
|
886
1000
|
"""
|
@@ -889,17 +1003,32 @@ class Daemon:
|
|
889
1003
|
if '_rotating_log' in self.__dict__:
|
890
1004
|
return self._rotating_log
|
891
1005
|
|
892
|
-
|
893
|
-
|
894
|
-
)
|
1006
|
+
logs_cf = self.properties.get('logs', None) or {}
|
1007
|
+
write_timestamps = logs_cf.get('write_timestamps', None)
|
895
1008
|
if write_timestamps is None:
|
896
1009
|
write_timestamps = get_config('jobs', 'logs', 'timestamps', 'enabled')
|
897
1010
|
|
1011
|
+
timestamp_format = logs_cf.get('timestamp_format', None)
|
1012
|
+
if timestamp_format is None:
|
1013
|
+
timestamp_format = get_config('jobs', 'logs', 'timestamps', 'format')
|
1014
|
+
|
1015
|
+
num_files_to_keep = logs_cf.get('num_files_to_keep', None)
|
1016
|
+
if num_files_to_keep is None:
|
1017
|
+
num_files_to_keep = get_config('jobs', 'logs', 'num_files_to_keep')
|
1018
|
+
|
1019
|
+
max_file_size = logs_cf.get('max_file_size', None)
|
1020
|
+
if max_file_size is None:
|
1021
|
+
max_file_size = get_config('jobs', 'logs', 'max_file_size')
|
1022
|
+
|
1023
|
+
redirect_streams = logs_cf.get('redirect_streams', True)
|
1024
|
+
|
898
1025
|
self._rotating_log = RotatingFile(
|
899
1026
|
self.log_path,
|
900
|
-
redirect_streams=
|
1027
|
+
redirect_streams=redirect_streams,
|
901
1028
|
write_timestamps=write_timestamps,
|
902
|
-
timestamp_format=
|
1029
|
+
timestamp_format=timestamp_format,
|
1030
|
+
num_files_to_keep=num_files_to_keep,
|
1031
|
+
max_file_size=max_file_size,
|
903
1032
|
)
|
904
1033
|
return self._rotating_log
|
905
1034
|
|
@@ -908,8 +1037,8 @@ class Daemon:
|
|
908
1037
|
"""
|
909
1038
|
Return the file handler for the stdin file.
|
910
1039
|
"""
|
911
|
-
if (
|
912
|
-
return
|
1040
|
+
if (_stdin_file := self.__dict__.get('_stdin_file', None)):
|
1041
|
+
return _stdin_file
|
913
1042
|
|
914
1043
|
self._stdin_file = StdinFile(
|
915
1044
|
self.stdin_file_path,
|
@@ -918,17 +1047,34 @@ class Daemon:
|
|
918
1047
|
return self._stdin_file
|
919
1048
|
|
920
1049
|
@property
|
921
|
-
def log_text(self) ->
|
1050
|
+
def log_text(self) -> Union[str, None]:
|
922
1051
|
"""
|
923
1052
|
Read the log files and return their contents.
|
924
1053
|
Returns `None` if the log file does not exist.
|
925
1054
|
"""
|
1055
|
+
logs_cf = self.properties.get('logs', None) or {}
|
1056
|
+
write_timestamps = logs_cf.get('write_timestamps', None)
|
1057
|
+
if write_timestamps is None:
|
1058
|
+
write_timestamps = get_config('jobs', 'logs', 'timestamps', 'enabled')
|
1059
|
+
|
1060
|
+
timestamp_format = logs_cf.get('timestamp_format', None)
|
1061
|
+
if timestamp_format is None:
|
1062
|
+
timestamp_format = get_config('jobs', 'logs', 'timestamps', 'format')
|
1063
|
+
|
1064
|
+
num_files_to_keep = logs_cf.get('num_files_to_keep', None)
|
1065
|
+
if num_files_to_keep is None:
|
1066
|
+
num_files_to_keep = get_config('jobs', 'logs', 'num_files_to_keep')
|
1067
|
+
|
1068
|
+
max_file_size = logs_cf.get('max_file_size', None)
|
1069
|
+
if max_file_size is None:
|
1070
|
+
max_file_size = get_config('jobs', 'logs', 'max_file_size')
|
1071
|
+
|
926
1072
|
new_rotating_log = RotatingFile(
|
927
1073
|
self.rotating_log.file_path,
|
928
|
-
num_files_to_keep
|
929
|
-
max_file_size
|
930
|
-
write_timestamps
|
931
|
-
timestamp_format
|
1074
|
+
num_files_to_keep=num_files_to_keep,
|
1075
|
+
max_file_size=max_file_size,
|
1076
|
+
write_timestamps=write_timestamps,
|
1077
|
+
timestamp_format=timestamp_format,
|
932
1078
|
)
|
933
1079
|
return new_rotating_log.read()
|
934
1080
|
|
@@ -953,20 +1099,25 @@ class Daemon:
|
|
953
1099
|
if not self.log_offset_path.exists():
|
954
1100
|
return 0, 0
|
955
1101
|
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
1102
|
+
try:
|
1103
|
+
with open(self.log_offset_path, 'r', encoding='utf-8') as f:
|
1104
|
+
cursor_text = f.read()
|
1105
|
+
cursor_parts = cursor_text.split(' ')
|
1106
|
+
subfile_index, subfile_position = int(cursor_parts[0]), int(cursor_parts[1])
|
1107
|
+
return subfile_index, subfile_position
|
1108
|
+
except Exception as e:
|
1109
|
+
warn(f"Failed to read cursor:\n{e}")
|
1110
|
+
return 0, 0
|
961
1111
|
|
962
1112
|
def _write_log_offset(self) -> None:
|
963
1113
|
"""
|
964
1114
|
Write the current log offset file.
|
965
1115
|
"""
|
966
|
-
with
|
967
|
-
|
968
|
-
|
969
|
-
|
1116
|
+
with self.log_offset_lock:
|
1117
|
+
with open(self.log_offset_path, 'w+', encoding='utf-8') as f:
|
1118
|
+
subfile_index = self.rotating_log._cursor[0]
|
1119
|
+
subfile_position = self.rotating_log._cursor[1]
|
1120
|
+
f.write(f"{subfile_index} {subfile_position}")
|
970
1121
|
|
971
1122
|
@property
|
972
1123
|
def pid(self) -> Union[int, None]:
|
@@ -1124,6 +1275,10 @@ class Daemon:
|
|
1124
1275
|
import pickle
|
1125
1276
|
import traceback
|
1126
1277
|
from meerschaum.utils.misc import generate_password
|
1278
|
+
|
1279
|
+
if not self.pickle:
|
1280
|
+
return True, "Success"
|
1281
|
+
|
1127
1282
|
backup_path = self.pickle_path.parent / (generate_password(7) + '.pkl')
|
1128
1283
|
try:
|
1129
1284
|
self.path.mkdir(parents=True, exist_ok=True)
|