You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

108 lines
2.8 KiB

  1. # -*- coding: utf-8 -*-
  2. # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
  3. # MIT License. See license.txt
  4. from __future__ import unicode_literals
  5. from datetime import datetime
  6. import json
  7. import traceback
  8. import frappe
  9. import os
  10. import uuid
  11. import rq
  12. MONITOR_REDIS_KEY = "monitor-transactions"
  13. MONITOR_MAX_ENTRIES = 1000000
  14. def start(transaction_type="request", method=None, kwargs=None):
  15. if frappe.conf.monitor:
  16. frappe.local.monitor = Monitor(transaction_type, method, kwargs)
  17. def stop(response=None):
  18. if frappe.conf.monitor and hasattr(frappe.local, "monitor"):
  19. frappe.local.monitor.dump(response)
  20. def log_file():
  21. return os.path.join(frappe.utils.get_bench_path(), "logs", "monitor.json.log")
  22. class Monitor:
  23. def __init__(self, transaction_type, method, kwargs):
  24. try:
  25. self.data = frappe._dict(
  26. {
  27. "site": frappe.local.site,
  28. "timestamp": datetime.utcnow(),
  29. "transaction_type": transaction_type,
  30. "uuid": str(uuid.uuid4()),
  31. }
  32. )
  33. if transaction_type == "request":
  34. self.collect_request_meta()
  35. else:
  36. self.collect_job_meta(method, kwargs)
  37. except Exception:
  38. traceback.print_exc()
  39. def collect_request_meta(self):
  40. self.data.request = frappe._dict(
  41. {
  42. "ip": frappe.local.request_ip,
  43. "method": frappe.request.method,
  44. "path": frappe.request.path,
  45. }
  46. )
  47. def collect_job_meta(self, method, kwargs):
  48. self.data.job = frappe._dict({"method": method, "scheduled": False, "wait": 0})
  49. if "run_scheduled_job" in method:
  50. self.data.job.method = kwargs["job_type"]
  51. self.data.job.scheduled = True
  52. job = rq.get_current_job()
  53. if job:
  54. self.data.uuid = job.id
  55. waitdiff = self.data.timestamp - job.enqueued_at
  56. self.data.job.wait = int(waitdiff.total_seconds() * 1000000)
  57. def dump(self, response=None):
  58. try:
  59. timediff = datetime.utcnow() - self.data.timestamp
  60. # Obtain duration in microseconds
  61. self.data.duration = int(timediff.total_seconds() * 1000000)
  62. if self.data.transaction_type == "request":
  63. self.data.request.status_code = response.status_code
  64. self.data.request.response_length = int(response.headers["Content-Length"])
  65. self.store()
  66. except Exception:
  67. traceback.print_exc()
  68. def store(self):
  69. if frappe.cache().llen(MONITOR_REDIS_KEY) > MONITOR_MAX_ENTRIES:
  70. frappe.cache().ltrim(MONITOR_REDIS_KEY, 1, -1)
  71. serialized = json.dumps(self.data, sort_keys=True, default=str)
  72. frappe.cache().rpush(MONITOR_REDIS_KEY, serialized)
  73. def flush():
  74. try:
  75. # Fetch all the logs without removing from cache
  76. logs = frappe.cache().lrange(MONITOR_REDIS_KEY, 0, -1)
  77. if logs:
  78. logs = list(map(frappe.safe_decode, logs))
  79. with open(log_file(), "a", os.O_NONBLOCK) as f:
  80. f.write("\n".join(logs))
  81. f.write("\n")
  82. # Remove fetched entries from cache
  83. frappe.cache().ltrim(MONITOR_REDIS_KEY, len(logs) - 1, -1)
  84. except Exception:
  85. traceback.print_exc()