seitime-frappe/frappe/monitor.py
Gavin D'souza 3446026555 chore: Update header: license.txt => LICENSE
The license.txt file has been replaced with LICENSE for quite a while
now. INAL but it didn't seem accurate to say "hey, checkout license.txt
although there's no such file". Apart from this, there were
inconsistencies in the headers altogether...this change brings
consistency.
2021-09-03 12:02:59 +05:30

111 lines
3 KiB
Python

# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
from datetime import datetime
import json
import traceback
import frappe
import os
import uuid
import rq
MONITOR_REDIS_KEY = "monitor-transactions"
MONITOR_MAX_ENTRIES = 1000000
def start(transaction_type="request", method=None, kwargs=None):
if frappe.conf.monitor:
frappe.local.monitor = Monitor(transaction_type, method, kwargs)
def stop(response=None):
if hasattr(frappe.local, "monitor"):
frappe.local.monitor.dump(response)
def log_file():
return os.path.join(frappe.utils.get_bench_path(), "logs", "monitor.json.log")
class Monitor:
def __init__(self, transaction_type, method, kwargs):
try:
self.data = frappe._dict(
{
"site": frappe.local.site,
"timestamp": datetime.utcnow(),
"transaction_type": transaction_type,
"uuid": str(uuid.uuid4()),
}
)
if transaction_type == "request":
self.collect_request_meta()
else:
self.collect_job_meta(method, kwargs)
except Exception:
traceback.print_exc()
def collect_request_meta(self):
self.data.request = frappe._dict(
{
"ip": frappe.local.request_ip,
"method": frappe.request.method,
"path": frappe.request.path,
}
)
def collect_job_meta(self, method, kwargs):
self.data.job = frappe._dict({"method": method, "scheduled": False, "wait": 0})
if "run_scheduled_job" in method:
self.data.job.method = kwargs["job_type"]
self.data.job.scheduled = True
job = rq.get_current_job()
if job:
self.data.uuid = job.id
waitdiff = self.data.timestamp - job.enqueued_at
self.data.job.wait = int(waitdiff.total_seconds() * 1000000)
def dump(self, response=None):
try:
timediff = datetime.utcnow() - self.data.timestamp
# Obtain duration in microseconds
self.data.duration = int(timediff.total_seconds() * 1000000)
if self.data.transaction_type == "request":
self.data.request.status_code = response.status_code
self.data.request.response_length = int(response.headers.get("Content-Length", 0))
if hasattr(frappe.local, "rate_limiter"):
limiter = frappe.local.rate_limiter
self.data.request.counter = limiter.counter
if limiter.rejected:
self.data.request.reset = limiter.reset
self.store()
except Exception:
traceback.print_exc()
def store(self):
if frappe.cache().llen(MONITOR_REDIS_KEY) > MONITOR_MAX_ENTRIES:
frappe.cache().ltrim(MONITOR_REDIS_KEY, 1, -1)
serialized = json.dumps(self.data, sort_keys=True, default=str)
frappe.cache().rpush(MONITOR_REDIS_KEY, serialized)
def flush():
try:
# Fetch all the logs without removing from cache
logs = frappe.cache().lrange(MONITOR_REDIS_KEY, 0, -1)
if logs:
logs = list(map(frappe.safe_decode, logs))
with open(log_file(), "a", os.O_NONBLOCK) as f:
f.write("\n".join(logs))
f.write("\n")
# Remove fetched entries from cache
frappe.cache().ltrim(MONITOR_REDIS_KEY, len(logs) - 1, -1)
except Exception:
traceback.print_exc()