Skip to content

Commit 4d5f91d

Browse files
committed
WIP
1 parent ec0be53 commit 4d5f91d

File tree

7 files changed

+156
-2
lines changed

7 files changed

+156
-2
lines changed

.github/workflows/publish-tag.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ jobs:
2828
service_account: github-actions-sa@ai-accounting-405809.iam.gserviceaccount.com
2929
access_token_lifetime: 600s
3030

31+
- name: Set application version
32+
run: |
33+
sed -i 's|version = "\(.*\)"|version = "${{ github.ref_name }}"|g' app/pyproject.toml &&
34+
cat app/pyproject.toml
35+
3136
- name: Setup Python
3237
uses: actions/setup-python@v3
3338

app/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "zcs-python-core"
7-
version = "0.3.0"
7+
version = "0.dev0"
88
description = "A Python library that provides core functionalities for ZCS projects"
99

1010
[tool.setuptools.packages.find]

app/src/zcs/core/logger/logger.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import json
22
import logging
3+
import time
4+
from logging import LogRecord
35

46
from zcs.core.exception import ZcsException
7+
from zcs.core.session import request_context, RequestState
58

69
old_factory = logging.getLogRecordFactory()
710

@@ -31,6 +34,9 @@ def record_factory(*args, **kwargs):
3134
class CloudJsonFormatter(logging.Formatter):
3235
def format(self, record):
3336

37+
# FIXME
38+
# log op_code, request_id and time deltas
39+
3440
message = record.getMessage()
3541
if record.exc_info and record.original_exception:
3642
message = str(record.original_exception)
@@ -79,8 +85,19 @@ def __init__(self, verbose=False):
7985
logging.CRITICAL: logging.Formatter(set_bold_red + base_format + reset)
8086
}
8187

82-
def format(self, record):
88+
def format(self, record: LogRecord) -> str:
8389
formatter = self.FORMATTERS.get(record.levelno)
90+
91+
# Retrieve request state from request context
92+
request_state: RequestState = request_context.get()
93+
if request_state:
94+
time_ns = time.perf_counter_ns()
95+
delta_ns = time_ns - request_state.getCheckpointNs()
96+
total_ns = time_ns - request_state.getRequestStartNs()
97+
request_state.setCheckpointNs(time_ns)
98+
record.msg = "[OP_CODE:{}][REQ_ID:{}] {} ({:.2f}s delta - {:.2f}s total)".format(
99+
request_state.getOpCode(), request_state.getRequestId(), record.msg, delta_ns / 1e9, total_ns / 1e9)
100+
84101
return formatter.format(record)
85102

86103

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .request_context import request_context
2+
from .operation_details import OperationDetails
3+
from .request_state import RequestState
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import time
2+
import logging
3+
from typing import Literal, Optional
4+
import uuid
5+
from dataclasses import dataclass
6+
7+
8+
@dataclass
9+
class OperationDetailsConfig:
10+
time_threshold_ms: int
11+
12+
13+
@dataclass
14+
class OperationDetails:
15+
"""
16+
Class used to record and manage detailed data about a specific operation.
17+
"""
18+
19+
def __init__(self, operation_code: str, logger: Optional[logging.Logger] = None):
20+
self.__operation_code = operation_code
21+
self.__logger = logger if logger else logging.getLogger()
22+
self.__start_time_ns = time.perf_counter_ns()
23+
self.__last_time_ns = self.__start_time_ns
24+
25+
def getStartTimeNs(self) -> int:
26+
return self.__start_time_ns
27+
28+
def getOpCode(self) -> str:
29+
return self.__operation_code
30+
31+
def logCheckpoint(self, message: Optional[str] = None, level: Literal[20] = logging.INFO):
32+
time_ns = time.perf_counter_ns()
33+
self.__logger.log(
34+
level,
35+
"[{}] {} ({:.2f}s delta - {:.2f}s total)".format(
36+
self.__operation_code,
37+
message,
38+
(time_ns - self.__last_time_ns) / 1e9,
39+
(time_ns - self.__start_time_ns) / 1e9
40+
)
41+
)
42+
self.__last_time_ns = time_ns
43+
44+
@staticmethod
45+
def create(op_code: Optional[str] = None, prefix: Optional[str] = None, logger=None) -> "OperationDetails":
46+
47+
# Use passed operation code if present, otherwise generate a new one
48+
operation_code = op_code if op_code else OperationDetails.generate_op_code(prefix)
49+
50+
operation_details = OperationDetails(operation_code, logger)
51+
52+
return operation_details
53+
54+
@staticmethod
55+
def generate_op_code(prefix: Optional[str] = None) -> str:
56+
"""
57+
Generate a new unique operation code.
58+
If prefix is provided, prepend it to the generated code.
59+
"""
60+
op_code = str(uuid.uuid4())
61+
if prefix:
62+
return f"{prefix}_{op_code}"
63+
return op_code
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import contextvars
2+
3+
request_context = contextvars.ContextVar("request_state", default=None)
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import time
2+
import uuid
3+
from typing import Optional
4+
5+
6+
class RequestState():
7+
8+
def __init__(
9+
self,
10+
request_id: Optional[str] = None,
11+
prefix: Optional[str] = None,
12+
op_code: Optional[str] = None):
13+
14+
self.__request_id = request_id if request_id else RequestState.generate_op_code(prefix=prefix)
15+
self.__op_code = op_code if op_code else self.__request_id
16+
self.__request_start_ns = time.perf_counter_ns()
17+
self.__checkpoint_ns = self.__request_start_ns
18+
19+
def getOpCode(self) -> str:
20+
"""
21+
Get operation code value.
22+
"""
23+
24+
return self.__op_code
25+
26+
def getRequestId(self) -> str:
27+
"""
28+
Get request id value.
29+
"""
30+
31+
return self.__request_id
32+
33+
def getRequestStartNs(self) -> int:
34+
"""
35+
Get request start time in nanoseconds.
36+
"""
37+
38+
return self.__request_start_ns
39+
40+
def getCheckpointNs(self) -> int:
41+
"""
42+
Get checkpoint time in nanoseconds.
43+
"""
44+
45+
return self.__checkpoint_ns
46+
47+
def setCheckpointNs(self, time_ns: int):
48+
"""
49+
Set checkpoint time in nanoseconds.
50+
"""
51+
52+
self.__checkpoint_ns = time_ns
53+
54+
@staticmethod
55+
def generate_op_code(prefix: Optional[str] = None) -> str:
56+
"""
57+
Generate a new unique operation code.
58+
If prefix is provided, prepend it to the generated code.
59+
"""
60+
op_code = str(uuid.uuid4())
61+
if prefix:
62+
return f"{prefix}_{op_code}"
63+
return op_code

0 commit comments

Comments
 (0)