ddeutil-workflow 0.0.84__py3-none-any.whl → 0.0.86__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.
@@ -17,6 +17,7 @@ Functions:
17
17
  set_logging: Configure logger with custom formatting.
18
18
  get_trace: Factory function for trace instances.
19
19
  """
20
+ import contextlib
20
21
  import json
21
22
  import logging
22
23
  import os
@@ -38,6 +39,7 @@ from typing import (
38
39
  Optional,
39
40
  TypeVar,
40
41
  Union,
42
+ cast,
41
43
  )
42
44
  from zoneinfo import ZoneInfo
43
45
 
@@ -56,7 +58,12 @@ EMJ_SKIP: str = "⏭️"
56
58
 
57
59
 
58
60
  @lru_cache
59
- def set_logging(name: str) -> logging.Logger:
61
+ def set_logging(
62
+ name: str,
63
+ *,
64
+ message_fmt: Optional[str] = None,
65
+ datetime_fmt: Optional[str] = None,
66
+ ) -> logging.Logger:
60
67
  """Configure logger with custom formatting and handlers.
61
68
 
62
69
  Creates and configures a logger instance with the custom formatter and
@@ -64,7 +71,9 @@ def set_logging(name: str) -> logging.Logger:
64
71
  console output and proper formatting for workflow execution tracking.
65
72
 
66
73
  Args:
67
- name: Module name to create logger for.
74
+ name (str): A module name to create logger for.
75
+ message_fmt: (str, default None)
76
+ datetime_fmt: (str, default None)
68
77
 
69
78
  Returns:
70
79
  logging.Logger: Configured logger instance with custom formatting.
@@ -81,9 +90,9 @@ def set_logging(name: str) -> logging.Logger:
81
90
  # `logging.getLogger('ddeutil.workflow').propagate = False`
82
91
  #
83
92
  _logger.addHandler(logging.NullHandler())
84
-
85
93
  formatter = logging.Formatter(
86
- fmt=config.log_format, datefmt=config.log_datetime_format
94
+ fmt=message_fmt,
95
+ datefmt=datetime_fmt,
87
96
  )
88
97
  stream_handler = logging.StreamHandler()
89
98
  stream_handler.setFormatter(formatter)
@@ -92,22 +101,34 @@ def set_logging(name: str) -> logging.Logger:
92
101
  return _logger
93
102
 
94
103
 
104
+ PrefixType = Literal[
105
+ "caller",
106
+ "nested",
107
+ "stage",
108
+ "job",
109
+ "workflow",
110
+ "release",
111
+ "schedule",
112
+ "audit",
113
+ ]
95
114
  PREFIX_LOGS: Final[dict[str, dict]] = {
96
- "CALLER": {
115
+ "caller": {
97
116
  "emoji": "⚙️",
98
117
  "desc": "logs from any usage from custom caller function.",
99
118
  },
100
- "NESTED": {"emoji": "⛓️", "desc": "logs from stages module."},
101
- "STAGE": {"emoji": "🔗", "desc": "logs from stages module."},
102
- "JOB": {"emoji": "🏗", "desc": "logs from job module."},
103
- "WORKFLOW": {"emoji": "👟", "desc": "logs from workflow module."},
104
- "RELEASE": {"emoji": "📅", "desc": "logs from release workflow method."},
105
- "POKING": {"emoji": "⏰", "desc": "logs from poke workflow method."},
106
- "AUDIT": {"emoji": "📌", "desc": "logs from audit model."},
119
+ "nested": {"emoji": "⛓️", "desc": "logs from stages module."},
120
+ "stage": {"emoji": "🔗", "desc": "logs from stages module."},
121
+ "job": {"emoji": "🏗", "desc": "logs from job module."},
122
+ "workflow": {"emoji": "👟", "desc": "logs from workflow module."},
123
+ "release": {"emoji": "📅", "desc": "logs from release workflow method."},
124
+ "schedule": {"emoji": "⏰", "desc": "logs from poke workflow method."},
125
+ "audit": {"emoji": "📌", "desc": "logs from audit model."},
107
126
  } # pragma: no cov
108
- PREFIX_DEFAULT: Final[str] = "CALLER"
127
+ PREFIX_LOGS_UPPER: Final[Iterator[str]] = (p.upper() for p in PREFIX_LOGS)
128
+ PREFIX_DEFAULT: Final[Literal["caller"]] = "caller"
129
+ PREFIX_EMOJI_DEFAULT: Final[str] = "⚙️"
109
130
  PREFIX_LOGS_REGEX: Final[re.Pattern[str]] = re.compile(
110
- rf"(^\[(?P<name>{'|'.join(PREFIX_LOGS)})]:\s?)?(?P<message>.*)",
131
+ rf"(^\[(?P<module>{'|'.join(PREFIX_LOGS_UPPER)})]:\s?)?(?P<message>.*)",
111
132
  re.MULTILINE | re.DOTALL | re.ASCII | re.VERBOSE,
112
133
  ) # pragma: no cov
113
134
 
@@ -119,42 +140,58 @@ class Message(BaseModel):
119
140
  with emoji support and categorization.
120
141
  """
121
142
 
122
- name: Optional[str] = Field(
123
- default=None, description="A prefix name of message."
143
+ module: Optional[PrefixType] = Field(
144
+ default=None,
145
+ description="A prefix module of message it allow to be None.",
124
146
  )
125
147
  message: Optional[str] = Field(default=None, description="A message.")
126
148
 
149
+ @field_validator("module", mode="before", json_schema_input_type=str)
150
+ def __prepare_module(cls, data: Optional[str]) -> Optional[str]:
151
+ return data.lower() if data is not None else data
152
+
127
153
  @classmethod
128
- def from_str(cls, msg: str) -> Self:
154
+ def from_str(cls, msg: str, module: Optional[PrefixType] = None) -> Self:
129
155
  """Extract message prefix from an input message.
130
156
 
131
157
  Args:
132
- msg: A message that want to extract.
158
+ msg (str): A message that want to extract.
159
+ module (PrefixType, default None): A prefix module type.
133
160
 
134
161
  Returns:
135
162
  Message: The validated model from a string message.
136
163
  """
137
- return Message.model_validate(
138
- obj=PREFIX_LOGS_REGEX.search(msg).groupdict()
139
- )
164
+ msg = cls.model_validate(PREFIX_LOGS_REGEX.search(msg).groupdict())
165
+ if msg.module is None and module:
166
+ msg.module = module
167
+ return msg
140
168
 
141
169
  def prepare(self, extras: Optional[DictData] = None) -> str:
142
170
  """Prepare message with force add prefix before writing trace log.
143
171
 
144
172
  Args:
145
- extras: An extra parameter that want to get the
146
- `log_add_emoji` flag.
173
+ extras (DictData, default None): An extra parameter that want to
174
+ get the `log_add_emoji` flag.
147
175
 
148
176
  Returns:
149
177
  str: The prepared message with prefix and optional emoji.
150
178
  """
151
- name: str = self.name or PREFIX_DEFAULT
179
+ module = cast(PrefixType, self.module or PREFIX_DEFAULT)
180
+ module_data: dict[str, str] = PREFIX_LOGS.get(
181
+ module, {"emoji": PREFIX_EMOJI_DEFAULT}
182
+ )
152
183
  emoji: str = (
153
- f"{PREFIX_LOGS[name]['emoji']} "
184
+ f"{module_data['emoji']} "
154
185
  if (extras or {}).get("log_add_emoji", True)
155
186
  else ""
156
187
  )
157
- return f"{emoji}[{name}]: {self.message}"
188
+ return f"{emoji}[{module.upper()}]: {self.message}"
189
+
190
+
191
+ class Metric(BaseModel): # pragma: no cov
192
+ """Trace Metric model that will validate data from current logging."""
193
+
194
+ execution_time: float
158
195
 
159
196
 
160
197
  class Metadata(BaseModel): # pragma: no cov
@@ -172,6 +209,9 @@ class Metadata(BaseModel): # pragma: no cov
172
209
  )
173
210
  process: int = Field(description="A process ID.")
174
211
  thread: int = Field(description="A thread ID.")
212
+ module: Optional[PrefixType] = Field(
213
+ default=None, description="A prefix module type."
214
+ )
175
215
  message: str = Field(description="A message log.")
176
216
  cut_id: Optional[str] = Field(
177
217
  default=None, description="A cutting of running ID."
@@ -181,17 +221,6 @@ class Metadata(BaseModel): # pragma: no cov
181
221
  filename: str = Field(description="A filename of this log.")
182
222
  lineno: int = Field(description="A line number of this log.")
183
223
 
184
- # Enhanced observability fields
185
- workflow_name: Optional[str] = Field(
186
- default=None, description="Name of the workflow being executed."
187
- )
188
- stage_name: Optional[str] = Field(
189
- default=None, description="Name of the current stage being executed."
190
- )
191
- job_name: Optional[str] = Field(
192
- default=None, description="Name of the current job being executed."
193
- )
194
-
195
224
  # Performance metrics
196
225
  duration_ms: Optional[float] = Field(
197
226
  default=None, description="Execution duration in milliseconds."
@@ -203,44 +232,6 @@ class Metadata(BaseModel): # pragma: no cov
203
232
  default=None, description="CPU usage percentage at log time."
204
233
  )
205
234
 
206
- # Distributed tracing support
207
- trace_id: Optional[str] = Field(
208
- default=None,
209
- description="OpenTelemetry trace ID for distributed tracing.",
210
- )
211
- span_id: Optional[str] = Field(
212
- default=None,
213
- description="OpenTelemetry span ID for distributed tracing.",
214
- )
215
- parent_span_id: Optional[str] = Field(
216
- default=None, description="Parent span ID for correlation."
217
- )
218
-
219
- # Error context
220
- exception_type: Optional[str] = Field(
221
- default=None, description="Exception class name if error occurred."
222
- )
223
- exception_message: Optional[str] = Field(
224
- default=None, description="Exception message if error occurred."
225
- )
226
- stack_trace: Optional[str] = Field(
227
- default=None, description="Full stack trace if error occurred."
228
- )
229
- error_code: Optional[str] = Field(
230
- default=None, description="Custom error code for categorization."
231
- )
232
-
233
- # Business context
234
- user_id: Optional[str] = Field(
235
- default=None, description="User ID who triggered the workflow."
236
- )
237
- tenant_id: Optional[str] = Field(
238
- default=None, description="Tenant ID for multi-tenant environments."
239
- )
240
- environment: Optional[str] = Field(
241
- default=None, description="Environment (dev, staging, prod)."
242
- )
243
-
244
235
  # NOTE: System context
245
236
  hostname: Optional[str] = Field(
246
237
  default=None, description="Hostname where workflow is running."
@@ -259,7 +250,7 @@ class Metadata(BaseModel): # pragma: no cov
259
250
  tags: Optional[list[str]] = Field(
260
251
  default_factory=list, description="Custom tags for categorization."
261
252
  )
262
- metadata: Optional[DictData] = Field(
253
+ metric: Optional[DictData] = Field(
263
254
  default_factory=dict, description="Additional custom metadata."
264
255
  )
265
256
 
@@ -299,6 +290,8 @@ class Metadata(BaseModel): # pragma: no cov
299
290
  run_id: str,
300
291
  parent_run_id: Optional[str],
301
292
  *,
293
+ metric: Optional[DictData] = None,
294
+ module: Optional[PrefixType] = None,
302
295
  extras: Optional[DictData] = None,
303
296
  ) -> Self:
304
297
  """Make the current metric for contract this Metadata model instance.
@@ -313,6 +306,8 @@ class Metadata(BaseModel): # pragma: no cov
313
306
  cutting_id: A cutting ID string.
314
307
  run_id:
315
308
  parent_run_id:
309
+ metric:
310
+ module:
316
311
  extras: An extra parameter that want to override core
317
312
  config values.
318
313
 
@@ -357,33 +352,17 @@ class Metadata(BaseModel): # pragma: no cov
357
352
  ),
358
353
  process=os.getpid(),
359
354
  thread=get_ident(),
355
+ module=module,
360
356
  message=message,
361
357
  cut_id=cutting_id,
362
358
  run_id=run_id,
363
359
  parent_run_id=parent_run_id,
364
360
  filename=frame_info.filename.split(os.path.sep)[-1],
365
361
  lineno=frame_info.lineno,
366
- # NOTE: Enhanced observability fields
367
- workflow_name=extras_data.get("workflow_name"),
368
- stage_name=extras_data.get("stage_name"),
369
- job_name=extras_data.get("job_name"),
370
362
  # NOTE: Performance metrics
371
363
  duration_ms=extras_data.get("duration_ms"),
372
364
  memory_usage_mb=extras_data.get("memory_usage_mb"),
373
365
  cpu_usage_percent=extras_data.get("cpu_usage_percent"),
374
- # NOTE: Distributed tracing support
375
- trace_id=extras_data.get("trace_id"),
376
- span_id=extras_data.get("span_id"),
377
- parent_span_id=extras_data.get("parent_span_id"),
378
- # NOTE: Error context
379
- exception_type=extras_data.get("exception_type"),
380
- exception_message=extras_data.get("exception_message"),
381
- stack_trace=extras_data.get("stack_trace"),
382
- error_code=extras_data.get("error_code"),
383
- # NOTE: Business context
384
- user_id=extras_data.get("user_id"),
385
- tenant_id=extras_data.get("tenant_id"),
386
- environment=extras_data.get("environment"),
387
366
  # NOTE: System context
388
367
  hostname=hostname,
389
368
  ip_address=ip_address,
@@ -391,11 +370,17 @@ class Metadata(BaseModel): # pragma: no cov
391
370
  package_version=__version__,
392
371
  # NOTE: Custom metadata
393
372
  tags=extras_data.get("tags", []),
394
- metadata=extras_data.get("metadata", {}),
373
+ metric=metric,
395
374
  )
396
375
 
397
376
  @property
398
- def pointer_id(self):
377
+ def pointer_id(self) -> str:
378
+ """Pointer ID of trace metadata.
379
+
380
+ Returns:
381
+ str: A pointer ID that will choose from parent running ID or running
382
+ ID.
383
+ """
399
384
  return self.parent_run_id or self.run_id
400
385
 
401
386
 
@@ -449,6 +434,27 @@ class ConsoleHandler(BaseHandler):
449
434
  """Console Handler model."""
450
435
 
451
436
  type: Literal["console"] = "console"
437
+ name: str = "ddeutil.workflow"
438
+ format: str = Field(
439
+ default=(
440
+ "%(asctime)s.%(msecs)03d (%(process)-5d, "
441
+ "%(thread)-5d) [%(levelname)-7s] (%(cut_id)s) %(message)-120s "
442
+ "(%(filename)s:%(lineno)s) (%(name)-10s)"
443
+ ),
444
+ description="A log format that will use with logging package.",
445
+ )
446
+ datetime_format: str = Field(
447
+ default="%Y-%m-%d %H:%M:%S",
448
+ description="A log datetime format.",
449
+ )
450
+
451
+ def pre(self) -> None:
452
+ """Pre-process."""
453
+ set_logging(
454
+ self.name,
455
+ message_fmt=self.format,
456
+ datetime_fmt=self.datetime_format,
457
+ )
452
458
 
453
459
  def emit(
454
460
  self, metadata: Metadata, *, extra: Optional[DictData] = None
@@ -512,6 +518,9 @@ class FileHandler(BaseHandler):
512
518
  return log_file
513
519
 
514
520
  def pre(self) -> None: # pragma: no cov
521
+ """Pre-method that will call from getting trace model factory function.
522
+ This method will create filepath of this parent log.
523
+ """
515
524
  if not (p := Path(self.path)).exists():
516
525
  p.mkdir(parents=True)
517
526
 
@@ -521,7 +530,12 @@ class FileHandler(BaseHandler):
521
530
  *,
522
531
  extra: Optional[DictData] = None,
523
532
  ) -> None:
524
- """Emit trace log."""
533
+ """Emit trace log to the file with a specific pointer path.
534
+
535
+ Args:
536
+ metadata (Metadata):
537
+ extra (DictData, default None):
538
+ """
525
539
  pointer: Path = self.pointer(metadata.pointer_id)
526
540
  std_file = "stderr" if metadata.error_flag else "stdout"
527
541
  with self._lock:
@@ -541,6 +555,7 @@ class FileHandler(BaseHandler):
541
555
  *,
542
556
  extra: Optional[DictData] = None,
543
557
  ) -> None: # pragma: no cove
558
+ """Async emit trace log."""
544
559
  try:
545
560
  import aiofiles
546
561
  except ImportError as e:
@@ -717,22 +732,9 @@ class SQLiteHandler(BaseHandler): # pragma: no cov
717
732
  filename TEXT NOT NULL,
718
733
  lineno INTEGER NOT NULL,
719
734
  cut_id TEXT,
720
- workflow_name TEXT,
721
- stage_name TEXT,
722
- job_name TEXT,
723
735
  duration_ms REAL,
724
736
  memory_usage_mb REAL,
725
737
  cpu_usage_percent REAL,
726
- trace_id TEXT,
727
- span_id TEXT,
728
- parent_span_id TEXT,
729
- exception_type TEXT,
730
- exception_message TEXT,
731
- stack_trace TEXT,
732
- error_code TEXT,
733
- user_id TEXT,
734
- tenant_id TEXT,
735
- environment TEXT,
736
738
  hostname TEXT,
737
739
  ip_address TEXT,
738
740
  python_version TEXT,
@@ -938,28 +940,15 @@ class SQLiteHandler(BaseHandler): # pragma: no cov
938
940
  cut_id=record[11],
939
941
  filename=record[9],
940
942
  lineno=record[10],
941
- workflow_name=record[12],
942
- stage_name=record[13],
943
- job_name=record[14],
944
943
  duration_ms=record[15],
945
944
  memory_usage_mb=record[16],
946
945
  cpu_usage_percent=record[17],
947
- trace_id=record[18],
948
- span_id=record[19],
949
- parent_span_id=record[20],
950
- exception_type=record[21],
951
- exception_message=record[22],
952
- stack_trace=record[23],
953
- error_code=record[24],
954
- user_id=record[25],
955
- tenant_id=record[26],
956
- environment=record[27],
957
946
  hostname=record[28],
958
947
  ip_address=record[29],
959
948
  python_version=record[30],
960
949
  package_version=record[31],
961
950
  tags=json.loads(record[32]) if record[32] else [],
962
- metadata=(
951
+ metric=(
963
952
  json.loads(record[33]) if record[33] else {}
964
953
  ),
965
954
  )
@@ -1045,28 +1034,15 @@ class SQLiteHandler(BaseHandler): # pragma: no cov
1045
1034
  cut_id=record[11],
1046
1035
  filename=record[9],
1047
1036
  lineno=record[10],
1048
- workflow_name=record[12],
1049
- stage_name=record[13],
1050
- job_name=record[14],
1051
1037
  duration_ms=record[15],
1052
1038
  memory_usage_mb=record[16],
1053
1039
  cpu_usage_percent=record[17],
1054
- trace_id=record[18],
1055
- span_id=record[19],
1056
- parent_span_id=record[20],
1057
- exception_type=record[21],
1058
- exception_message=record[22],
1059
- stack_trace=record[23],
1060
- error_code=record[24],
1061
- user_id=record[25],
1062
- tenant_id=record[26],
1063
- environment=record[27],
1064
1040
  hostname=record[28],
1065
1041
  ip_address=record[29],
1066
1042
  python_version=record[30],
1067
1043
  package_version=record[31],
1068
1044
  tags=json.loads(record[32]) if record[32] else [],
1069
- metadata=json.loads(record[33]) if record[33] else {},
1045
+ metric=json.loads(record[33]) if record[33] else {},
1070
1046
  )
1071
1047
 
1072
1048
  meta_list.append(trace_meta)
@@ -1394,22 +1370,9 @@ class ElasticHandler(BaseHandler): # pragma: no cov
1394
1370
  "filename": {"type": "keyword"},
1395
1371
  "lineno": {"type": "integer"},
1396
1372
  "cut_id": {"type": "keyword"},
1397
- "workflow_name": {"type": "keyword"},
1398
- "stage_name": {"type": "keyword"},
1399
- "job_name": {"type": "keyword"},
1400
1373
  "duration_ms": {"type": "float"},
1401
1374
  "memory_usage_mb": {"type": "float"},
1402
1375
  "cpu_usage_percent": {"type": "float"},
1403
- "trace_id": {"type": "keyword"},
1404
- "span_id": {"type": "keyword"},
1405
- "parent_span_id": {"type": "keyword"},
1406
- "exception_type": {"type": "keyword"},
1407
- "exception_message": {"type": "text"},
1408
- "stack_trace": {"type": "text"},
1409
- "error_code": {"type": "keyword"},
1410
- "user_id": {"type": "keyword"},
1411
- "tenant_id": {"type": "keyword"},
1412
- "environment": {"type": "keyword"},
1413
1376
  "hostname": {"type": "keyword"},
1414
1377
  "ip_address": {"type": "ip"},
1415
1378
  "python_version": {"type": "keyword"},
@@ -1453,22 +1416,9 @@ class ElasticHandler(BaseHandler): # pragma: no cov
1453
1416
  "filename": base_data["filename"],
1454
1417
  "lineno": base_data["lineno"],
1455
1418
  "cut_id": base_data["cut_id"],
1456
- "workflow_name": base_data.get("workflow_name"),
1457
- "stage_name": base_data.get("stage_name"),
1458
- "job_name": base_data.get("job_name"),
1459
1419
  "duration_ms": base_data.get("duration_ms"),
1460
1420
  "memory_usage_mb": base_data.get("memory_usage_mb"),
1461
1421
  "cpu_usage_percent": base_data.get("cpu_usage_percent"),
1462
- "trace_id": base_data.get("trace_id"),
1463
- "span_id": base_data.get("span_id"),
1464
- "parent_span_id": base_data.get("parent_span_id"),
1465
- "exception_type": base_data.get("exception_type"),
1466
- "exception_message": base_data.get("exception_message"),
1467
- "stack_trace": base_data.get("stack_trace"),
1468
- "error_code": base_data.get("error_code"),
1469
- "user_id": base_data.get("user_id"),
1470
- "tenant_id": base_data.get("tenant_id"),
1471
- "environment": base_data.get("environment"),
1472
1422
  "hostname": base_data.get("hostname"),
1473
1423
  "ip_address": base_data.get("ip_address"),
1474
1424
  "python_version": base_data.get("python_version"),
@@ -1587,28 +1537,15 @@ class ElasticHandler(BaseHandler): # pragma: no cov
1587
1537
  cut_id=source.get("cut_id"),
1588
1538
  filename=source["filename"],
1589
1539
  lineno=source["lineno"],
1590
- workflow_name=source.get("workflow_name"),
1591
- stage_name=source.get("stage_name"),
1592
- job_name=source.get("job_name"),
1593
1540
  duration_ms=source.get("duration_ms"),
1594
1541
  memory_usage_mb=source.get("memory_usage_mb"),
1595
1542
  cpu_usage_percent=source.get("cpu_usage_percent"),
1596
- trace_id=source.get("trace_id"),
1597
- span_id=source.get("span_id"),
1598
- parent_span_id=source.get("parent_span_id"),
1599
- exception_type=source.get("exception_type"),
1600
- exception_message=source.get("exception_message"),
1601
- stack_trace=source.get("stack_trace"),
1602
- error_code=source.get("error_code"),
1603
- user_id=source.get("user_id"),
1604
- tenant_id=source.get("tenant_id"),
1605
- environment=source.get("environment"),
1606
1543
  hostname=source.get("hostname"),
1607
1544
  ip_address=source.get("ip_address"),
1608
1545
  python_version=source.get("python_version"),
1609
1546
  package_version=source.get("package_version"),
1610
1547
  tags=source.get("tags", []),
1611
- metadata=source.get("metadata", {}),
1548
+ metric=source.get("metric", {}),
1612
1549
  )
1613
1550
 
1614
1551
  meta_list.append(trace_meta)
@@ -1697,28 +1634,15 @@ class ElasticHandler(BaseHandler): # pragma: no cov
1697
1634
  cut_id=source.get("cut_id"),
1698
1635
  filename=source["filename"],
1699
1636
  lineno=source["lineno"],
1700
- workflow_name=source.get("workflow_name"),
1701
- stage_name=source.get("stage_name"),
1702
- job_name=source.get("job_name"),
1703
1637
  duration_ms=source.get("duration_ms"),
1704
1638
  memory_usage_mb=source.get("memory_usage_mb"),
1705
1639
  cpu_usage_percent=source.get("cpu_usage_percent"),
1706
- trace_id=source.get("trace_id"),
1707
- span_id=source.get("span_id"),
1708
- parent_span_id=source.get("parent_span_id"),
1709
- exception_type=source.get("exception_type"),
1710
- exception_message=source.get("exception_message"),
1711
- stack_trace=source.get("stack_trace"),
1712
- error_code=source.get("error_code"),
1713
- user_id=source.get("user_id"),
1714
- tenant_id=source.get("tenant_id"),
1715
- environment=source.get("environment"),
1716
1640
  hostname=source.get("hostname"),
1717
1641
  ip_address=source.get("ip_address"),
1718
1642
  python_version=source.get("python_version"),
1719
1643
  package_version=source.get("package_version"),
1720
1644
  tags=source.get("tags", []),
1721
- metadata=source.get("metadata", {}),
1645
+ metric=source.get("metric", {}),
1722
1646
  )
1723
1647
 
1724
1648
  meta_list.append(trace_meta)
@@ -1774,71 +1698,94 @@ class BaseEmit(ABC):
1774
1698
  self,
1775
1699
  msg: str,
1776
1700
  level: Level,
1777
- ):
1701
+ *,
1702
+ metric: Optional[DictData] = None,
1703
+ module: Optional[PrefixType] = None,
1704
+ ) -> None:
1778
1705
  """Write trace log with append mode and logging this message with any
1779
1706
  logging level.
1780
1707
 
1781
1708
  Args:
1782
1709
  msg: A message that want to log.
1783
1710
  level: A logging level.
1711
+ metric (DictData, default None): A metric data that want to export
1712
+ to each target handler.
1713
+ module (PrefixType, default None): A module name that use for adding
1714
+ prefix at the message value.
1784
1715
  """
1785
1716
  raise NotImplementedError(
1786
- "Logging action should be implement for making trace log."
1717
+ "Emit action should be implement for making trace log."
1787
1718
  )
1788
1719
 
1789
- def debug(self, msg: str):
1720
+ def debug(self, msg: str, module: Optional[PrefixType] = None):
1790
1721
  """Write trace log with append mode and logging this message with the
1791
1722
  DEBUG level.
1792
1723
 
1793
1724
  Args:
1794
1725
  msg: A message that want to log.
1726
+ module (PrefixType, default None): A module name that use for adding
1727
+ prefix at the message value.
1795
1728
  """
1796
- self.emit(msg, level="debug")
1729
+ self.emit(msg, level="debug", module=module)
1797
1730
 
1798
- def info(self, msg: str) -> None:
1731
+ def info(self, msg: str, module: Optional[PrefixType] = None) -> None:
1799
1732
  """Write trace log with append mode and logging this message with the
1800
1733
  INFO level.
1801
1734
 
1802
1735
  Args:
1803
1736
  msg: A message that want to log.
1737
+ module (PrefixType, default None): A module name that use for adding
1738
+ prefix at the message value.
1804
1739
  """
1805
- self.emit(msg, level="info")
1740
+ self.emit(msg, level="info", module=module)
1806
1741
 
1807
- def warning(self, msg: str) -> None:
1742
+ def warning(self, msg: str, module: Optional[PrefixType] = None) -> None:
1808
1743
  """Write trace log with append mode and logging this message with the
1809
1744
  WARNING level.
1810
1745
 
1811
1746
  Args:
1812
1747
  msg: A message that want to log.
1748
+ module (PrefixType, default None): A module name that use for adding
1749
+ prefix at the message value.
1813
1750
  """
1814
- self.emit(msg, level="warning")
1751
+ self.emit(msg, level="warning", module=module)
1815
1752
 
1816
- def error(self, msg: str) -> None:
1753
+ def error(self, msg: str, module: Optional[PrefixType] = None) -> None:
1817
1754
  """Write trace log with append mode and logging this message with the
1818
1755
  ERROR level.
1819
1756
 
1820
1757
  Args:
1821
1758
  msg: A message that want to log.
1759
+ module (PrefixType, default None): A module name that use for adding
1760
+ prefix at the message value.
1822
1761
  """
1823
- self.emit(msg, level="error")
1762
+ self.emit(msg, level="error", module=module)
1824
1763
 
1825
- def exception(self, msg: str) -> None:
1764
+ def exception(self, msg: str, module: Optional[PrefixType] = None) -> None:
1826
1765
  """Write trace log with append mode and logging this message with the
1827
1766
  EXCEPTION level.
1828
1767
 
1829
1768
  Args:
1830
1769
  msg: A message that want to log.
1770
+ module (PrefixType, default None): A module name that use for adding
1771
+ prefix at the message value.
1831
1772
  """
1832
- self.emit(msg, level="exception")
1773
+ self.emit(msg, level="exception", module=module)
1833
1774
 
1834
1775
 
1835
1776
  class BaseAsyncEmit(ABC):
1777
+ """Base Async Emit Abstract class for mixin `amit` method and async
1778
+ logging that will use prefix with `a` charactor.
1779
+ """
1836
1780
 
1837
1781
  @abstractmethod
1838
1782
  async def amit(
1839
1783
  self,
1840
1784
  msg: str,
1841
1785
  level: Level,
1786
+ *,
1787
+ metric: Optional[DictData] = None,
1788
+ module: Optional[PrefixType] = None,
1842
1789
  ) -> None:
1843
1790
  """Async write trace log with append mode and logging this message with
1844
1791
  any logging level.
@@ -1846,55 +1793,79 @@ class BaseAsyncEmit(ABC):
1846
1793
  Args:
1847
1794
  msg (str): A message that want to log.
1848
1795
  level (Mode): A logging level.
1796
+ metric (DictData, default None): A metric data that want to export
1797
+ to each target handler.
1798
+ module (PrefixType, default None): A module name that use for adding
1799
+ prefix at the message value.
1849
1800
  """
1850
1801
  raise NotImplementedError(
1851
1802
  "Async Logging action should be implement for making trace log."
1852
1803
  )
1853
1804
 
1854
- async def adebug(self, msg: str) -> None: # pragma: no cov
1805
+ async def adebug(
1806
+ self, msg: str, module: Optional[PrefixType] = None
1807
+ ) -> None: # pragma: no cov
1855
1808
  """Async write trace log with append mode and logging this message with
1856
1809
  the DEBUG level.
1857
1810
 
1858
1811
  Args:
1859
1812
  msg: A message that want to log.
1813
+ module (PrefixType, default None): A module name that use for adding
1814
+ prefix at the message value.
1860
1815
  """
1861
- await self.amit(msg, level="debug")
1816
+ await self.amit(msg, level="debug", module=module)
1862
1817
 
1863
- async def ainfo(self, msg: str) -> None: # pragma: no cov
1818
+ async def ainfo(
1819
+ self, msg: str, module: Optional[PrefixType] = None
1820
+ ) -> None: # pragma: no cov
1864
1821
  """Async write trace log with append mode and logging this message with
1865
1822
  the INFO level.
1866
1823
 
1867
1824
  Args:
1868
1825
  msg: A message that want to log.
1826
+ module (PrefixType, default None): A module name that use for adding
1827
+ prefix at the message value.
1869
1828
  """
1870
- await self.amit(msg, level="info")
1829
+ await self.amit(msg, level="info", module=module)
1871
1830
 
1872
- async def awarning(self, msg: str) -> None: # pragma: no cov
1831
+ async def awarning(
1832
+ self, msg: str, module: Optional[PrefixType] = None
1833
+ ) -> None: # pragma: no cov
1873
1834
  """Async write trace log with append mode and logging this message with
1874
1835
  the WARNING level.
1875
1836
 
1876
1837
  Args:
1877
1838
  msg: A message that want to log.
1839
+ module (PrefixType, default None): A module name that use for adding
1840
+ prefix at the message value.
1878
1841
  """
1879
- await self.amit(msg, level="warning")
1842
+ await self.amit(msg, level="warning", module=module)
1880
1843
 
1881
- async def aerror(self, msg: str) -> None: # pragma: no cov
1844
+ async def aerror(
1845
+ self, msg: str, module: Optional[PrefixType] = None
1846
+ ) -> None: # pragma: no cov
1882
1847
  """Async write trace log with append mode and logging this message with
1883
1848
  the ERROR level.
1884
1849
 
1885
1850
  Args:
1886
1851
  msg: A message that want to log.
1852
+ module (PrefixType, default None): A module name that use for adding
1853
+ prefix at the message value.
1887
1854
  """
1888
- await self.amit(msg, level="error")
1855
+ await self.amit(msg, level="error", module=module)
1889
1856
 
1890
- async def aexception(self, msg: str) -> None: # pragma: no cov
1857
+ async def aexception(
1858
+ self, msg: str, module: Optional[PrefixType] = None
1859
+ ) -> None: # pragma: no cov
1891
1860
  """Async write trace log with append mode and logging this message with
1892
1861
  the EXCEPTION level.
1893
1862
 
1894
1863
  Args:
1895
1864
  msg: A message that want to log.
1865
+ module (PrefixType, default None): A module name that use for adding
1866
+ prefix at the message value.
1896
1867
  """
1897
- await self.amit(msg, level="exception")
1868
+ await self.amit(msg, level="exception", module=module)
1898
1869
 
1899
1870
 
1900
1871
  class Trace(BaseModel, BaseEmit, BaseAsyncEmit):
@@ -1940,35 +1911,41 @@ class Trace(BaseModel, BaseEmit, BaseAsyncEmit):
1940
1911
  cut_parent_run_id: str = cut_id(self.parent_run_id)
1941
1912
  return f"{cut_parent_run_id} -> {cut_run_id}"
1942
1913
 
1943
- def make_message(self, msg: str) -> str:
1944
- """Prepare and Make a message before write and log steps.
1945
-
1946
- Args:
1947
- msg: A message that want to prepare and make before.
1948
-
1949
- Returns:
1950
- str: The prepared message.
1951
- """
1952
- return prepare_newline(Message.from_str(msg).prepare(self.extras))
1953
-
1954
- def emit(self, msg: str, level: Level) -> None:
1914
+ def emit(
1915
+ self,
1916
+ msg: str,
1917
+ level: Level,
1918
+ *,
1919
+ metric: Optional[DictData] = None,
1920
+ module: Optional[PrefixType] = None,
1921
+ ) -> None:
1955
1922
  """Emit a trace log to all handler. This will use synchronise process.
1956
1923
 
1957
1924
  Args:
1958
1925
  msg (str): A message.
1959
1926
  level (Level): A tracing level.
1927
+ metric (DictData, default None): A metric data that want to export
1928
+ to each target handler.
1929
+ module (PrefixType, default None): A module name that use for adding
1930
+ prefix at the message value.
1960
1931
  """
1961
- _msg: str = self.make_message(msg)
1932
+ _msg: Message = Message.from_str(msg, module=module)
1962
1933
  metadata: Metadata = Metadata.make(
1963
1934
  error_flag=(level in ("error", "exception")),
1964
1935
  level=level,
1965
- message=_msg,
1936
+ module=_msg.module,
1937
+ message=prepare_newline(_msg.prepare(self.extras)),
1966
1938
  cutting_id=self.cut_id,
1967
1939
  run_id=self.run_id,
1968
1940
  parent_run_id=self.parent_run_id,
1941
+ metric=metric,
1969
1942
  extras=self.extras,
1970
1943
  )
1944
+
1945
+ # NOTE: Check enable buffer flag was set or not.
1971
1946
  if not self._enable_buffer:
1947
+
1948
+ # NOTE: Start emit tracing log data to each handler.
1972
1949
  for handler in self.handlers:
1973
1950
  handler.emit(metadata, extra=self.extras)
1974
1951
  return
@@ -1981,56 +1958,76 @@ class Trace(BaseModel, BaseEmit, BaseAsyncEmit):
1981
1958
  handler.flush(self._buffer, extra=self.extras)
1982
1959
  self._buffer.clear()
1983
1960
 
1984
- async def amit(self, msg: str, level: Level) -> None:
1961
+ async def amit(
1962
+ self,
1963
+ msg: str,
1964
+ level: Level,
1965
+ *,
1966
+ metric: Optional[DictData] = None,
1967
+ module: Optional[PrefixType] = None,
1968
+ ) -> None:
1985
1969
  """Async write trace log with append mode and logging this message with
1986
1970
  any logging level.
1987
1971
 
1988
1972
  Args:
1989
1973
  msg (str): A message that want to log.
1990
1974
  level (Level): A logging mode.
1975
+ metric (DictData, default None): A metric data that want to export
1976
+ to each target handler.
1977
+ module (PrefixType, default None): A module name that use for adding
1978
+ prefix at the message value.
1991
1979
  """
1992
- _msg: str = self.make_message(msg)
1980
+ _msg: Message = Message.from_str(msg, module=module)
1993
1981
  metadata: Metadata = Metadata.make(
1994
1982
  error_flag=(level in ("error", "exception")),
1995
1983
  level=level,
1996
- message=_msg,
1984
+ module=_msg.module,
1985
+ message=prepare_newline(_msg.prepare(self.extras)),
1997
1986
  cutting_id=self.cut_id,
1998
1987
  run_id=self.run_id,
1999
1988
  parent_run_id=self.parent_run_id,
1989
+ metric=metric,
2000
1990
  extras=self.extras,
2001
1991
  )
1992
+
1993
+ # NOTE: Start emit tracing log data to each handler.
2002
1994
  for handler in self.handlers:
2003
1995
  await handler.amit(metadata, extra=self.extras)
2004
1996
 
2005
- def __enter__(self):
1997
+ @contextlib.contextmanager
1998
+ def buffer(self, module: Optional[PrefixType] = None) -> Iterator[Self]:
2006
1999
  """Enter the trace for catching the logs that run so fast. It will use
2007
2000
  buffer strategy to flush the logs instead emit.
2001
+
2002
+ Args:
2003
+ module (PrefixType, default None): A module name that use for adding
2004
+ prefix at the message value.
2005
+
2006
+ Yields:
2007
+ Self: Itself instance.
2008
2008
  """
2009
2009
  self._enable_buffer = True
2010
- return self
2011
-
2012
- def __exit__(self, exc_type, exc_val, exc_tb):
2013
- """Exit the trace that will clear all log in the buffer."""
2014
- if exc_type:
2015
- _msg: str = self.make_message(str(exc_val))
2010
+ try:
2011
+ yield self
2012
+ except Exception as err:
2013
+ _msg: Message = Message.from_str(str(err), module=module)
2016
2014
  metadata: Metadata = Metadata.make(
2017
2015
  error_flag=True,
2018
2016
  level="error",
2019
- message=_msg,
2017
+ module=_msg.module,
2018
+ message=prepare_newline(_msg.prepare(self.extras)),
2020
2019
  cutting_id=self.cut_id,
2021
2020
  run_id=self.run_id,
2022
2021
  parent_run_id=self.parent_run_id,
2023
2022
  extras=self.extras,
2024
2023
  )
2025
2024
  self._buffer.append(metadata)
2026
-
2027
- if self._buffer:
2028
- for handler in self.handlers:
2029
- handler.flush(self._buffer, extra=self.extras)
2030
- self._buffer.clear()
2031
-
2032
- # NOTE: Re-raise the exception if one occurred
2033
- return False
2025
+ raise
2026
+ finally:
2027
+ if self._buffer:
2028
+ for handler in self.handlers:
2029
+ handler.flush(self._buffer, extra=self.extras)
2030
+ self._buffer.clear()
2034
2031
 
2035
2032
 
2036
2033
  def get_trace(
@@ -2039,38 +2036,39 @@ def get_trace(
2039
2036
  handlers: list[Union[DictData, Handler]] = None,
2040
2037
  parent_run_id: Optional[str] = None,
2041
2038
  extras: Optional[DictData] = None,
2042
- auto_pre_process: bool = False,
2039
+ pre_process: bool = False,
2043
2040
  ) -> Trace:
2044
- """Get dynamic Trace instance from the core config.
2041
+ """Get dynamic Trace instance from the core config. This function will use
2042
+ for start some process, and it wants to generated trace object.
2045
2043
 
2046
- This factory function returns the appropriate trace implementation based on
2047
- configuration. It can be overridden by extras argument and accepts running ID
2048
- and parent running ID.
2044
+ This factory function returns the appropriate trace implementation based
2045
+ on configuration. It can be overridden by extras argument and accepts
2046
+ running ID and parent running ID.
2049
2047
 
2050
2048
  Args:
2051
2049
  run_id (str): A running ID.
2052
- parent_run_id (str | None, default None): A parent running ID.
2053
- handlers (list):
2054
- extras: An extra parameter that want to override the core
2055
- config values.
2056
- auto_pre_process (bool, default False)
2050
+ parent_run_id (str, default None): A parent running ID.
2051
+ handlers (list[DictData | Handler], default None): A list of handler or
2052
+ mapping of handler data that want to direct pass instead use
2053
+ environment variable config.
2054
+ extras (DictData, default None): An extra parameter that want to
2055
+ override the core config values.
2056
+ pre_process (bool, default False) A flag that will auto call pre
2057
+ method after validate a trace model.
2057
2058
 
2058
2059
  Returns:
2059
2060
  Trace: The appropriate trace instance.
2060
2061
  """
2061
- handlers: list[DictData] = dynamic(
2062
- "trace_handlers", f=handlers, extras=extras
2063
- )
2064
2062
  trace: Trace = Trace.model_validate(
2065
2063
  {
2066
2064
  "run_id": run_id,
2067
2065
  "parent_run_id": parent_run_id,
2068
- "handlers": handlers,
2066
+ "handlers": dynamic("trace_handlers", f=handlers, extras=extras),
2069
2067
  "extras": extras or {},
2070
2068
  }
2071
2069
  )
2072
2070
  # NOTE: Start pre-process when start create trace.
2073
- if auto_pre_process:
2071
+ if pre_process:
2074
2072
  for handler in trace.handlers:
2075
2073
  handler.pre()
2076
2074
  return trace