loggerbuf 0.9.2__tar.gz → 0.9.3__tar.gz
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.
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/PKG-INFO +57 -11
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/README.md +56 -10
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/pyproject.toml +1 -1
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/src/loggerbuf/data_logs/main_data_pb2.py +13 -9
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/src/loggerbuf/data_logs/registry_pb2.py +11 -10
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/src/loggerbuf/telemetry.py +101 -16
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/src/loggerbuf.egg-info/PKG-INFO +57 -11
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/tests/test_telemetry.py +34 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/setup.cfg +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/src/loggerbuf/__init__.py +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/src/loggerbuf/cli/__init__.py +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/src/loggerbuf/cli/console.py +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/src/loggerbuf/cli/handlers/__init__.py +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/src/loggerbuf/cli/handlers/decode.py +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/src/loggerbuf/cli/handlers/events.py +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/src/loggerbuf/cli/handlers/fields.py +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/src/loggerbuf/cli/handlers/protos.py +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/src/loggerbuf/cli/handlers/stress.py +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/src/loggerbuf/cli/utils/__init__.py +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/src/loggerbuf/cli/utils/registry.py +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/src/loggerbuf/cli/utils/schema_validator.py +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/src/loggerbuf/config.py +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/src/loggerbuf/data_logs/__init__.py +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/src/loggerbuf/debugger.py +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/src/loggerbuf/queue_metrics.py +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/src/loggerbuf/schema_loader.py +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/src/loggerbuf.egg-info/SOURCES.txt +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/src/loggerbuf.egg-info/dependency_links.txt +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/src/loggerbuf.egg-info/entry_points.txt +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/src/loggerbuf.egg-info/requires.txt +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/src/loggerbuf.egg-info/top_level.txt +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/tests/test_cli_decode_debug.py +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/tests/test_config.py +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/tests/test_console_cli.py +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/tests/test_create_classes.py +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/tests/test_debugger.py +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/tests/test_debugger_coverage.py +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/tests/test_decoder.py +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/tests/test_events_cli.py +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/tests/test_json_formatter.py +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/tests/test_protos_coverage.py +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/tests/test_queue_metrics.py +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/tests/test_schema_validator.py +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/tests/test_telemetry_counters.py +0 -0
- {loggerbuf-0.9.2 → loggerbuf-0.9.3}/tests/test_telemetry_coverage.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: loggerbuf
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.3
|
|
4
4
|
Summary: A powerful structured telemetry and debugging logger with Protobuf schemas
|
|
5
5
|
Author-email: LoggerBuf Team <hello@loggerbuf.dev>
|
|
6
6
|
Project-URL: Repository, https://github.com/calavraian/LoggerBuf
|
|
@@ -28,6 +28,8 @@ Requires-Dist: psutil
|
|
|
28
28
|
[](https://opensource.org/licenses/MIT)
|
|
29
29
|
[](https://github.com/calavraian/LoggerBuf/actions)
|
|
30
30
|
|
|
31
|
+
**[Visit the official website: loggerbuf.dev](https://loggerbuf.dev)**
|
|
32
|
+
|
|
31
33
|
</div>
|
|
32
34
|
|
|
33
35
|
🇺🇸 English | 🇪🇸 [Español](README.es.md)
|
|
@@ -64,6 +66,15 @@ You are now ready to log! You can also run these sub-commands individually if yo
|
|
|
64
66
|
|
|
65
67
|
---
|
|
66
68
|
|
|
69
|
+
## 🤖 AI Agent Integration (Agent Skill)
|
|
70
|
+
|
|
71
|
+
LoggerBuf is incredibly easy for AI coding assistants (like Gemini, Claude, or Cursor) to use!
|
|
72
|
+
We provide a dedicated **Agent Skill** (`SKILL.md`) located in `.agents/skills/loggerbuf/SKILL.md` within this repository.
|
|
73
|
+
|
|
74
|
+
If you are using an agentic AI system that supports custom skills, simply point it to our `SKILL.md`. Your agent will instantly understand the CLI workflow, Schema Management rules, Event formatting guidelines, and DRY Python implementation patterns required to integrate LoggerBuf seamlessly into your project.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
67
78
|
## 🤔 Why LoggerBuf? (The Problem & The Solution)
|
|
68
79
|
|
|
69
80
|
**The Problem:**
|
|
@@ -169,22 +180,57 @@ The custom formatter instantly reveals the *where* and *what* of any log entry w
|
|
|
169
180
|
- `message::> [Payload]`: The actual log message or serialized JSON object.
|
|
170
181
|
|
|
171
182
|
### 2. Analytical Event Logging (Telemetry)
|
|
172
|
-
Telemetry uses Protobuf. Every event
|
|
183
|
+
Telemetry uses Protobuf to log structured events. Every event is categorized using `EventType` and `EventStatus`. LoggerBuf automatically injects timestamps, file names, lines, and system identifiers.
|
|
184
|
+
|
|
185
|
+
**Way 1: Standard Method (`log_event`)**
|
|
186
|
+
The simplest way to log an event. You can pass any field defined in your `main_data.proto` as a keyword argument (kwargs).
|
|
173
187
|
|
|
174
188
|
```python
|
|
175
|
-
from telemetry import TelemetryLog
|
|
176
|
-
|
|
189
|
+
from loggerbuf.telemetry import TelemetryLog
|
|
190
|
+
|
|
191
|
+
telemetry = TelemetryLog("MAIN")
|
|
192
|
+
|
|
193
|
+
# Fire an event asynchronously
|
|
194
|
+
telemetry.log_event(
|
|
195
|
+
event_type="EVENT_DATA_BASE_PROCESSING",
|
|
196
|
+
general_note="User uploaded a new invoice",
|
|
197
|
+
status="STATUS_COMPLETED",
|
|
198
|
+
duration_ms=150
|
|
199
|
+
)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**Way 2: Context Manager (`event_context`)**
|
|
203
|
+
Ideal for tracking block execution. It automatically calculates `duration_ms` and will set the status to `STATUS_ERROR` if an unhandled exception occurs inside the block.
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
from loggerbuf.telemetry import TelemetryLog
|
|
207
|
+
|
|
208
|
+
telemetry = TelemetryLog("MAIN")
|
|
209
|
+
|
|
210
|
+
with telemetry.event_context(general_note="Processing database migration"):
|
|
211
|
+
# ... your code here ...
|
|
212
|
+
# duration_ms is automatically tracked and the event is fired when exiting the block
|
|
213
|
+
pass
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
**Way 3: Manual Object Building (`send`)**
|
|
217
|
+
If you need absolute control, you can build the Protobuf object manually:
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
from loggerbuf.telemetry import TelemetryLog
|
|
221
|
+
from loggerbuf import schema_loader
|
|
222
|
+
|
|
223
|
+
main_data_pb2 = schema_loader.get_main_data_pb2()
|
|
224
|
+
registry_pb2 = schema_loader.get_registry_pb2()
|
|
177
225
|
|
|
178
|
-
# 1. Initialize Logger
|
|
179
226
|
telemetry = TelemetryLog("MAIN")
|
|
180
227
|
|
|
181
|
-
|
|
182
|
-
event =
|
|
183
|
-
event.
|
|
184
|
-
event.
|
|
185
|
-
event.status = EventStatus.STATUS_COMPLETED
|
|
228
|
+
event = main_data_pb2.Event()
|
|
229
|
+
event.event_type = registry_pb2.EventType.EVENT_DATA_BASE_PROCESSING
|
|
230
|
+
event.general_note = "User uploaded a new invoice"
|
|
231
|
+
event.status = registry_pb2.EventStatus.STATUS_PENDING
|
|
186
232
|
|
|
187
|
-
#
|
|
233
|
+
# Fire the event asynchronously
|
|
188
234
|
telemetry.send(event)
|
|
189
235
|
```
|
|
190
236
|
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
[](https://opensource.org/licenses/MIT)
|
|
8
8
|
[](https://github.com/calavraian/LoggerBuf/actions)
|
|
9
9
|
|
|
10
|
+
**[Visit the official website: loggerbuf.dev](https://loggerbuf.dev)**
|
|
11
|
+
|
|
10
12
|
</div>
|
|
11
13
|
|
|
12
14
|
🇺🇸 English | 🇪🇸 [Español](README.es.md)
|
|
@@ -43,6 +45,15 @@ You are now ready to log! You can also run these sub-commands individually if yo
|
|
|
43
45
|
|
|
44
46
|
---
|
|
45
47
|
|
|
48
|
+
## 🤖 AI Agent Integration (Agent Skill)
|
|
49
|
+
|
|
50
|
+
LoggerBuf is incredibly easy for AI coding assistants (like Gemini, Claude, or Cursor) to use!
|
|
51
|
+
We provide a dedicated **Agent Skill** (`SKILL.md`) located in `.agents/skills/loggerbuf/SKILL.md` within this repository.
|
|
52
|
+
|
|
53
|
+
If you are using an agentic AI system that supports custom skills, simply point it to our `SKILL.md`. Your agent will instantly understand the CLI workflow, Schema Management rules, Event formatting guidelines, and DRY Python implementation patterns required to integrate LoggerBuf seamlessly into your project.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
46
57
|
## 🤔 Why LoggerBuf? (The Problem & The Solution)
|
|
47
58
|
|
|
48
59
|
**The Problem:**
|
|
@@ -148,22 +159,57 @@ The custom formatter instantly reveals the *where* and *what* of any log entry w
|
|
|
148
159
|
- `message::> [Payload]`: The actual log message or serialized JSON object.
|
|
149
160
|
|
|
150
161
|
### 2. Analytical Event Logging (Telemetry)
|
|
151
|
-
Telemetry uses Protobuf. Every event
|
|
162
|
+
Telemetry uses Protobuf to log structured events. Every event is categorized using `EventType` and `EventStatus`. LoggerBuf automatically injects timestamps, file names, lines, and system identifiers.
|
|
163
|
+
|
|
164
|
+
**Way 1: Standard Method (`log_event`)**
|
|
165
|
+
The simplest way to log an event. You can pass any field defined in your `main_data.proto` as a keyword argument (kwargs).
|
|
152
166
|
|
|
153
167
|
```python
|
|
154
|
-
from telemetry import TelemetryLog
|
|
155
|
-
|
|
168
|
+
from loggerbuf.telemetry import TelemetryLog
|
|
169
|
+
|
|
170
|
+
telemetry = TelemetryLog("MAIN")
|
|
171
|
+
|
|
172
|
+
# Fire an event asynchronously
|
|
173
|
+
telemetry.log_event(
|
|
174
|
+
event_type="EVENT_DATA_BASE_PROCESSING",
|
|
175
|
+
general_note="User uploaded a new invoice",
|
|
176
|
+
status="STATUS_COMPLETED",
|
|
177
|
+
duration_ms=150
|
|
178
|
+
)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Way 2: Context Manager (`event_context`)**
|
|
182
|
+
Ideal for tracking block execution. It automatically calculates `duration_ms` and will set the status to `STATUS_ERROR` if an unhandled exception occurs inside the block.
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
from loggerbuf.telemetry import TelemetryLog
|
|
186
|
+
|
|
187
|
+
telemetry = TelemetryLog("MAIN")
|
|
188
|
+
|
|
189
|
+
with telemetry.event_context(general_note="Processing database migration"):
|
|
190
|
+
# ... your code here ...
|
|
191
|
+
# duration_ms is automatically tracked and the event is fired when exiting the block
|
|
192
|
+
pass
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**Way 3: Manual Object Building (`send`)**
|
|
196
|
+
If you need absolute control, you can build the Protobuf object manually:
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
from loggerbuf.telemetry import TelemetryLog
|
|
200
|
+
from loggerbuf import schema_loader
|
|
201
|
+
|
|
202
|
+
main_data_pb2 = schema_loader.get_main_data_pb2()
|
|
203
|
+
registry_pb2 = schema_loader.get_registry_pb2()
|
|
156
204
|
|
|
157
|
-
# 1. Initialize Logger
|
|
158
205
|
telemetry = TelemetryLog("MAIN")
|
|
159
206
|
|
|
160
|
-
|
|
161
|
-
event =
|
|
162
|
-
event.
|
|
163
|
-
event.
|
|
164
|
-
event.status = EventStatus.STATUS_COMPLETED
|
|
207
|
+
event = main_data_pb2.Event()
|
|
208
|
+
event.event_type = registry_pb2.EventType.EVENT_DATA_BASE_PROCESSING
|
|
209
|
+
event.general_note = "User uploaded a new invoice"
|
|
210
|
+
event.status = registry_pb2.EventStatus.STATUS_PENDING
|
|
165
211
|
|
|
166
|
-
#
|
|
212
|
+
# Fire the event asynchronously
|
|
167
213
|
telemetry.send(event)
|
|
168
214
|
```
|
|
169
215
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
# WARNING: DO NOT EDIT THIS FILE MANUALLY.
|
|
1
2
|
# -*- coding: utf-8 -*-
|
|
2
3
|
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
4
|
# NO CHECKED-IN PROTOBUF GENCODE
|
|
4
5
|
# source: main_data.proto
|
|
5
|
-
# Protobuf Python Version:
|
|
6
|
+
# Protobuf Python Version: 7.35.0
|
|
6
7
|
"""Generated protocol buffer code."""
|
|
7
8
|
from google.protobuf import descriptor as _descriptor
|
|
8
9
|
from google.protobuf import descriptor_pool as _descriptor_pool
|
|
@@ -11,9 +12,9 @@ from google.protobuf import symbol_database as _symbol_database
|
|
|
11
12
|
from google.protobuf.internal import builder as _builder
|
|
12
13
|
_runtime_version.ValidateProtobufRuntimeVersion(
|
|
13
14
|
_runtime_version.Domain.PUBLIC,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
7,
|
|
16
|
+
35,
|
|
17
|
+
0,
|
|
17
18
|
'',
|
|
18
19
|
'main_data.proto'
|
|
19
20
|
)
|
|
@@ -22,10 +23,13 @@ _runtime_version.ValidateProtobufRuntimeVersion(
|
|
|
22
23
|
_sym_db = _symbol_database.Default()
|
|
23
24
|
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
try:
|
|
27
|
+
from . import registry_pb2 as registry__pb2
|
|
28
|
+
except ImportError:
|
|
29
|
+
import registry_pb2 as registry__pb2
|
|
26
30
|
|
|
27
31
|
|
|
28
|
-
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0fmain_data.proto\x12\tmain_data\x1a\x0eregistry.proto\"\
|
|
32
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0fmain_data.proto\x12\tmain_data\x1a\x0eregistry.proto\"\xd5\x02\n\x05\x45vent\x12+\n\nevent_type\x18\x01 \x01(\x0e\x32\x17.enum_control.EventType\x12\x11\n\ttimestamp\x18\x02 \x01(\t\x12\x14\n\x0cgeneral_note\x18\x03 \x01(\t\x12)\n\x06status\x18\x04 \x01(\x0e\x32\x19.enum_control.EventStatus\x12\x13\n\x0blogger_name\x18\x06 \x01(\t\x12\x13\n\x0b\x63\x61ller_file\x18\x07 \x01(\t\x12\x14\n\x0c\x63\x61ller_class\x18\x08 \x01(\t\x12\x17\n\x0f\x63\x61ller_function\x18\t \x01(\t\x12\x0e\n\x06lineno\x18\n \x01(\x05\x12\x13\n\x0b\x64uration_ms\x18\x0b \x01(\x03\x12\x1b\n\x12previous_file_hash\x18\x8dN \x01(\x0c\x12\x17\n\x0eis_chain_start\x18\x8eN \x01(\x08\x12\x17\n\x0ehmac_signature\x18\x8fN \x01(\x0c\"\x99\x02\n\x0c\x43ounterEvent\x12/\n\x0c\x63ounter_type\x18\x01 \x01(\x0e\x32\x19.enum_control.CounterType\x12\x11\n\ttimestamp\x18\x02 \x01(\t\x12\r\n\x05\x63ount\x18\x03 \x01(\x05\x12\x13\n\x0blogger_name\x18\x04 \x01(\t\x12\x13\n\x0b\x63\x61ller_file\x18\x05 \x01(\t\x12\x14\n\x0c\x63\x61ller_class\x18\x06 \x01(\t\x12\x17\n\x0f\x63\x61ller_function\x18\x07 \x01(\t\x12\x0e\n\x06lineno\x18\x08 \x01(\x05\x12\x1b\n\x12previous_file_hash\x18\x8dN \x01(\x0c\x12\x17\n\x0eis_chain_start\x18\x8eN \x01(\x08\x12\x17\n\x0ehmac_signature\x18\x8fN \x01(\x0c\x62\x06proto3')
|
|
29
33
|
|
|
30
34
|
_globals = globals()
|
|
31
35
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
@@ -33,7 +37,7 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'main_data_pb2', _globals)
|
|
|
33
37
|
if not _descriptor._USE_C_DESCRIPTORS:
|
|
34
38
|
DESCRIPTOR._loaded_options = None
|
|
35
39
|
_globals['_EVENT']._serialized_start=47
|
|
36
|
-
_globals['_EVENT']._serialized_end=
|
|
37
|
-
_globals['_COUNTEREVENT']._serialized_start=
|
|
38
|
-
_globals['_COUNTEREVENT']._serialized_end=
|
|
40
|
+
_globals['_EVENT']._serialized_end=388
|
|
41
|
+
_globals['_COUNTEREVENT']._serialized_start=391
|
|
42
|
+
_globals['_COUNTEREVENT']._serialized_end=672
|
|
39
43
|
# @@protoc_insertion_point(module_scope)
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
# WARNING: DO NOT EDIT THIS FILE MANUALLY.
|
|
1
2
|
# -*- coding: utf-8 -*-
|
|
2
3
|
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
4
|
# NO CHECKED-IN PROTOBUF GENCODE
|
|
4
5
|
# source: registry.proto
|
|
5
|
-
# Protobuf Python Version:
|
|
6
|
+
# Protobuf Python Version: 7.35.0
|
|
6
7
|
"""Generated protocol buffer code."""
|
|
7
8
|
from google.protobuf import descriptor as _descriptor
|
|
8
9
|
from google.protobuf import descriptor_pool as _descriptor_pool
|
|
@@ -11,9 +12,9 @@ from google.protobuf import symbol_database as _symbol_database
|
|
|
11
12
|
from google.protobuf.internal import builder as _builder
|
|
12
13
|
_runtime_version.ValidateProtobufRuntimeVersion(
|
|
13
14
|
_runtime_version.Domain.PUBLIC,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
7,
|
|
16
|
+
35,
|
|
17
|
+
0,
|
|
17
18
|
'',
|
|
18
19
|
'registry.proto'
|
|
19
20
|
)
|
|
@@ -24,7 +25,7 @@ _sym_db = _symbol_database.Default()
|
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
|
|
27
|
-
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0eregistry.proto\x12\x0c\x65num_control*\
|
|
28
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0eregistry.proto\x12\x0c\x65num_control*\xe7\x01\n\x0b\x45ventStatus\x12\x16\n\x12STATUS_UNSPECIFIED\x10\x00\x12\x12\n\x0eSTATUS_PENDING\x10\x01\x12\x11\n\rSTATUS_ACTIVE\x10\x02\x12\x14\n\x10STATUS_COMPLETED\x10\x03\x12\x11\n\rSTATUS_FAILED\x10\x04\x12\x12\n\x0eSTATUS_ABORTED\x10\x05\x12\x1d\n\x19\x44\x45MO_EVENT_STATUS_STARTED\x10\x33\x12\x1f\n\x1b\x44\x45MO_EVENT_STATUS_COMPLETED\x10\x34\x12\x1c\n\x18\x44\x45MO_EVENT_STATUS_FAILED\x10\x35*\x88\x02\n\tEventType\x12\x15\n\x11\x45VENT_UNSPECIFIED\x10\x00\x12\x11\n\rEVENT_GENERIC\x10\x01\x12\x0e\n\nEVENT_MAIN\x10\x02\x12\x1e\n\x1a\x45VENT_DATA_BASE_PROCESSING\x10\x03\x12\x15\n\x11\x45VENT_API_REQUEST\x10\x04\x12\x16\n\x12\x45VENT_API_RESPONSE\x10\x05\x12\x1a\n\x16\x44\x45MO_EVENT_API_INVOKED\x10\x65\x12\x1b\n\x17\x44\x45MO_EVENT_API_RESPONSE\x10\x66\x12\x1d\n\x19\x44\x45MO_EVENT_DATA_PROCESSED\x10h\x12\x1a\n\x16\x44\x45MO_EVENT_DATA_STORED\x10i*;\n\x0b\x43ounterType\x12\x17\n\x13\x43OUNTER_UNSPECIFIED\x10\x00\x12\x13\n\x0f\x43OUNTER_GENERIC\x10\x01\x62\x06proto3')
|
|
28
29
|
|
|
29
30
|
_globals = globals()
|
|
30
31
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
@@ -32,9 +33,9 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'registry_pb2', _globals)
|
|
|
32
33
|
if not _descriptor._USE_C_DESCRIPTORS:
|
|
33
34
|
DESCRIPTOR._loaded_options = None
|
|
34
35
|
_globals['_EVENTSTATUS']._serialized_start=33
|
|
35
|
-
_globals['_EVENTSTATUS']._serialized_end=
|
|
36
|
-
_globals['_EVENTTYPE']._serialized_start=
|
|
37
|
-
_globals['_EVENTTYPE']._serialized_end=
|
|
38
|
-
_globals['_COUNTERTYPE']._serialized_start=
|
|
39
|
-
_globals['_COUNTERTYPE']._serialized_end=
|
|
36
|
+
_globals['_EVENTSTATUS']._serialized_end=264
|
|
37
|
+
_globals['_EVENTTYPE']._serialized_start=267
|
|
38
|
+
_globals['_EVENTTYPE']._serialized_end=531
|
|
39
|
+
_globals['_COUNTERTYPE']._serialized_start=533
|
|
40
|
+
_globals['_COUNTERTYPE']._serialized_end=592
|
|
40
41
|
# @@protoc_insertion_point(module_scope)
|
|
@@ -8,11 +8,40 @@ from .config import ConfigManager, QueueStrategy, ConfigKey
|
|
|
8
8
|
|
|
9
9
|
from . import schema_loader
|
|
10
10
|
main_data_pb2 = schema_loader.get_main_data_pb2()
|
|
11
|
+
registry_pb2 = schema_loader.get_registry_pb2()
|
|
11
12
|
Event = main_data_pb2.Event
|
|
12
13
|
CounterEvent = main_data_pb2.CounterEvent
|
|
13
14
|
|
|
14
15
|
from .queue_metrics import QueueMetrics, MetricField
|
|
15
16
|
|
|
17
|
+
class EventContext:
|
|
18
|
+
def __init__(self, telemetry_instance, event_type, base_kwargs):
|
|
19
|
+
self.telemetry = telemetry_instance
|
|
20
|
+
self.event_type = event_type
|
|
21
|
+
self.kwargs = base_kwargs
|
|
22
|
+
self.start_time = None
|
|
23
|
+
|
|
24
|
+
def attach(self, **new_kwargs):
|
|
25
|
+
"""Attaches additional data to the event context."""
|
|
26
|
+
self.kwargs.update(new_kwargs)
|
|
27
|
+
|
|
28
|
+
def __enter__(self):
|
|
29
|
+
self.start_time = time.time()
|
|
30
|
+
return self
|
|
31
|
+
|
|
32
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
33
|
+
duration_ms = int((time.time() - self.start_time) * 1000)
|
|
34
|
+
self.kwargs["duration_ms"] = duration_ms
|
|
35
|
+
|
|
36
|
+
if exc_type is not None:
|
|
37
|
+
status = registry_pb2.EventStatus.STATUS_FAILED
|
|
38
|
+
self.kwargs["general_note"] = f"[{exc_type.__name__}] {str(exc_value)} | {self.kwargs.get('general_note', '')}"
|
|
39
|
+
else:
|
|
40
|
+
status = registry_pb2.EventStatus.STATUS_COMPLETED
|
|
41
|
+
|
|
42
|
+
self.telemetry.log_event(event_type=self.event_type, status=status, stack_depth=3, **self.kwargs)
|
|
43
|
+
return False
|
|
44
|
+
|
|
16
45
|
class BaseSettings:
|
|
17
46
|
def __init__(self, prefix: str, name: str = None, logs_base_dir: str = ".", backup_dir: str = None, file_size: int = None, backup_count: int = None):
|
|
18
47
|
config = ConfigManager()
|
|
@@ -310,17 +339,67 @@ class TelemetryLog:
|
|
|
310
339
|
self.__event_writer = TelemetryLog.__instances[name][1]
|
|
311
340
|
self.__metric_writer = TelemetryLog.__instances[name][2]
|
|
312
341
|
|
|
313
|
-
def
|
|
342
|
+
def event_context(self, event_type=None, **kwargs):
|
|
343
|
+
"""Returns a context manager for tracking an event's duration and status."""
|
|
344
|
+
if event_type is None:
|
|
345
|
+
event_type = registry_pb2.EventType.EVENT_GENERIC
|
|
346
|
+
return EventContext(self, event_type, kwargs)
|
|
347
|
+
|
|
348
|
+
def log_event(self, event_type=None, status=None, stack_depth=2, **kwargs):
|
|
349
|
+
"""Convenience method to log an event directly with kwargs."""
|
|
350
|
+
if event_type is None:
|
|
351
|
+
event_type = registry_pb2.EventType.EVENT_GENERIC
|
|
352
|
+
if status is None:
|
|
353
|
+
status = registry_pb2.EventStatus.STATUS_COMPLETED
|
|
354
|
+
|
|
355
|
+
main_event = Event()
|
|
356
|
+
main_event.event_type = event_type
|
|
357
|
+
main_event.status = status
|
|
358
|
+
|
|
359
|
+
# Protected fields that the system handles
|
|
360
|
+
protected_fields = {"logger_name", "timestamp", "caller_file", "caller_class", "caller_function", "lineno"}
|
|
361
|
+
|
|
362
|
+
for key, value in kwargs.items():
|
|
363
|
+
if key in protected_fields:
|
|
364
|
+
continue
|
|
365
|
+
|
|
366
|
+
if hasattr(main_event, key):
|
|
367
|
+
field = getattr(main_event, key)
|
|
368
|
+
if hasattr(field, 'CopyFrom'):
|
|
369
|
+
try:
|
|
370
|
+
field.CopyFrom(value)
|
|
371
|
+
except Exception as e:
|
|
372
|
+
# Fallback for some types or error
|
|
373
|
+
pass
|
|
374
|
+
else:
|
|
375
|
+
try:
|
|
376
|
+
setattr(main_event, key, value)
|
|
377
|
+
except Exception:
|
|
378
|
+
pass
|
|
379
|
+
else:
|
|
380
|
+
# Issue warning via debugger if enabled, or just pass
|
|
381
|
+
pass
|
|
382
|
+
|
|
383
|
+
self.create_event(main_event, stack_depth=stack_depth)
|
|
384
|
+
|
|
385
|
+
def create_event(self, event: Event, stack_depth: int = 1):
|
|
314
386
|
import sys
|
|
315
387
|
try:
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
388
|
+
# Walk up the stack to find the actual caller outside telemetry.py
|
|
389
|
+
frame = sys._getframe(stack_depth)
|
|
390
|
+
while frame and os.path.basename(frame.f_code.co_filename) == "telemetry.py":
|
|
391
|
+
frame = frame.f_back
|
|
392
|
+
|
|
393
|
+
if frame:
|
|
394
|
+
event.caller_file = os.path.basename(frame.f_code.co_filename)
|
|
395
|
+
event.caller_function = frame.f_code.co_name
|
|
396
|
+
event.lineno = frame.f_lineno
|
|
397
|
+
if 'self' in frame.f_locals:
|
|
398
|
+
event.caller_class = frame.f_locals['self'].__class__.__name__
|
|
399
|
+
else:
|
|
400
|
+
event.caller_class = "None"
|
|
322
401
|
else:
|
|
323
|
-
|
|
402
|
+
raise ValueError("No caller frame found")
|
|
324
403
|
except Exception:
|
|
325
404
|
event.caller_file = "Unknown"
|
|
326
405
|
event.caller_function = "Unknown"
|
|
@@ -347,7 +426,7 @@ class TelemetryLog:
|
|
|
347
426
|
_check_deprecated_fields(event)
|
|
348
427
|
self.__event_writer.write_event(event)
|
|
349
428
|
|
|
350
|
-
def increment(self, counter_type, value: int = 1):
|
|
429
|
+
def increment(self, counter_type, value: int = 1, stack_depth: int = 1):
|
|
351
430
|
if not self.__metric_writer:
|
|
352
431
|
return
|
|
353
432
|
|
|
@@ -357,14 +436,20 @@ class TelemetryLog:
|
|
|
357
436
|
|
|
358
437
|
import sys
|
|
359
438
|
try:
|
|
360
|
-
frame = sys._getframe(
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
if
|
|
365
|
-
event.
|
|
439
|
+
frame = sys._getframe(stack_depth)
|
|
440
|
+
while frame and os.path.basename(frame.f_code.co_filename) == "telemetry.py":
|
|
441
|
+
frame = frame.f_back
|
|
442
|
+
|
|
443
|
+
if frame:
|
|
444
|
+
event.caller_file = os.path.basename(frame.f_code.co_filename)
|
|
445
|
+
event.caller_function = frame.f_code.co_name
|
|
446
|
+
event.lineno = frame.f_lineno
|
|
447
|
+
if 'self' in frame.f_locals:
|
|
448
|
+
event.caller_class = frame.f_locals['self'].__class__.__name__
|
|
449
|
+
else:
|
|
450
|
+
event.caller_class = "None"
|
|
366
451
|
else:
|
|
367
|
-
|
|
452
|
+
raise ValueError("No caller frame found")
|
|
368
453
|
except Exception:
|
|
369
454
|
event.caller_file = "Unknown"
|
|
370
455
|
event.caller_function = "Unknown"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: loggerbuf
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.3
|
|
4
4
|
Summary: A powerful structured telemetry and debugging logger with Protobuf schemas
|
|
5
5
|
Author-email: LoggerBuf Team <hello@loggerbuf.dev>
|
|
6
6
|
Project-URL: Repository, https://github.com/calavraian/LoggerBuf
|
|
@@ -28,6 +28,8 @@ Requires-Dist: psutil
|
|
|
28
28
|
[](https://opensource.org/licenses/MIT)
|
|
29
29
|
[](https://github.com/calavraian/LoggerBuf/actions)
|
|
30
30
|
|
|
31
|
+
**[Visit the official website: loggerbuf.dev](https://loggerbuf.dev)**
|
|
32
|
+
|
|
31
33
|
</div>
|
|
32
34
|
|
|
33
35
|
🇺🇸 English | 🇪🇸 [Español](README.es.md)
|
|
@@ -64,6 +66,15 @@ You are now ready to log! You can also run these sub-commands individually if yo
|
|
|
64
66
|
|
|
65
67
|
---
|
|
66
68
|
|
|
69
|
+
## 🤖 AI Agent Integration (Agent Skill)
|
|
70
|
+
|
|
71
|
+
LoggerBuf is incredibly easy for AI coding assistants (like Gemini, Claude, or Cursor) to use!
|
|
72
|
+
We provide a dedicated **Agent Skill** (`SKILL.md`) located in `.agents/skills/loggerbuf/SKILL.md` within this repository.
|
|
73
|
+
|
|
74
|
+
If you are using an agentic AI system that supports custom skills, simply point it to our `SKILL.md`. Your agent will instantly understand the CLI workflow, Schema Management rules, Event formatting guidelines, and DRY Python implementation patterns required to integrate LoggerBuf seamlessly into your project.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
67
78
|
## 🤔 Why LoggerBuf? (The Problem & The Solution)
|
|
68
79
|
|
|
69
80
|
**The Problem:**
|
|
@@ -169,22 +180,57 @@ The custom formatter instantly reveals the *where* and *what* of any log entry w
|
|
|
169
180
|
- `message::> [Payload]`: The actual log message or serialized JSON object.
|
|
170
181
|
|
|
171
182
|
### 2. Analytical Event Logging (Telemetry)
|
|
172
|
-
Telemetry uses Protobuf. Every event
|
|
183
|
+
Telemetry uses Protobuf to log structured events. Every event is categorized using `EventType` and `EventStatus`. LoggerBuf automatically injects timestamps, file names, lines, and system identifiers.
|
|
184
|
+
|
|
185
|
+
**Way 1: Standard Method (`log_event`)**
|
|
186
|
+
The simplest way to log an event. You can pass any field defined in your `main_data.proto` as a keyword argument (kwargs).
|
|
173
187
|
|
|
174
188
|
```python
|
|
175
|
-
from telemetry import TelemetryLog
|
|
176
|
-
|
|
189
|
+
from loggerbuf.telemetry import TelemetryLog
|
|
190
|
+
|
|
191
|
+
telemetry = TelemetryLog("MAIN")
|
|
192
|
+
|
|
193
|
+
# Fire an event asynchronously
|
|
194
|
+
telemetry.log_event(
|
|
195
|
+
event_type="EVENT_DATA_BASE_PROCESSING",
|
|
196
|
+
general_note="User uploaded a new invoice",
|
|
197
|
+
status="STATUS_COMPLETED",
|
|
198
|
+
duration_ms=150
|
|
199
|
+
)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**Way 2: Context Manager (`event_context`)**
|
|
203
|
+
Ideal for tracking block execution. It automatically calculates `duration_ms` and will set the status to `STATUS_ERROR` if an unhandled exception occurs inside the block.
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
from loggerbuf.telemetry import TelemetryLog
|
|
207
|
+
|
|
208
|
+
telemetry = TelemetryLog("MAIN")
|
|
209
|
+
|
|
210
|
+
with telemetry.event_context(general_note="Processing database migration"):
|
|
211
|
+
# ... your code here ...
|
|
212
|
+
# duration_ms is automatically tracked and the event is fired when exiting the block
|
|
213
|
+
pass
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
**Way 3: Manual Object Building (`send`)**
|
|
217
|
+
If you need absolute control, you can build the Protobuf object manually:
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
from loggerbuf.telemetry import TelemetryLog
|
|
221
|
+
from loggerbuf import schema_loader
|
|
222
|
+
|
|
223
|
+
main_data_pb2 = schema_loader.get_main_data_pb2()
|
|
224
|
+
registry_pb2 = schema_loader.get_registry_pb2()
|
|
177
225
|
|
|
178
|
-
# 1. Initialize Logger
|
|
179
226
|
telemetry = TelemetryLog("MAIN")
|
|
180
227
|
|
|
181
|
-
|
|
182
|
-
event =
|
|
183
|
-
event.
|
|
184
|
-
event.
|
|
185
|
-
event.status = EventStatus.STATUS_COMPLETED
|
|
228
|
+
event = main_data_pb2.Event()
|
|
229
|
+
event.event_type = registry_pb2.EventType.EVENT_DATA_BASE_PROCESSING
|
|
230
|
+
event.general_note = "User uploaded a new invoice"
|
|
231
|
+
event.status = registry_pb2.EventStatus.STATUS_PENDING
|
|
186
232
|
|
|
187
|
-
#
|
|
233
|
+
# Fire the event asynchronously
|
|
188
234
|
telemetry.send(event)
|
|
189
235
|
```
|
|
190
236
|
|
|
@@ -96,3 +96,37 @@ def test_telemetry_concurrency_lossless(tmp_path):
|
|
|
96
96
|
count += 1
|
|
97
97
|
|
|
98
98
|
assert count == 1000
|
|
99
|
+
|
|
100
|
+
def test_telemetry_log_event(tmp_path):
|
|
101
|
+
settings = EventSettings(name=f"TEST_LOG_EVENT_{tmp_path.name}", logs_base_dir=str(tmp_path))
|
|
102
|
+
telemetry = TelemetryLog(settings)
|
|
103
|
+
|
|
104
|
+
# Test setting a standard field
|
|
105
|
+
telemetry.log_event(general_note="Test log_event")
|
|
106
|
+
|
|
107
|
+
writer = telemetry._TelemetryLog__event_writer
|
|
108
|
+
writer.queue.join()
|
|
109
|
+
time.sleep(0.1)
|
|
110
|
+
|
|
111
|
+
def test_telemetry_event_context_success(tmp_path):
|
|
112
|
+
settings = EventSettings(name=f"TEST_CTX_SUCCESS_{tmp_path.name}", logs_base_dir=str(tmp_path))
|
|
113
|
+
telemetry = TelemetryLog(settings)
|
|
114
|
+
|
|
115
|
+
with telemetry.event_context(general_note="Context Test"):
|
|
116
|
+
time.sleep(0.05)
|
|
117
|
+
|
|
118
|
+
writer = telemetry._TelemetryLog__event_writer
|
|
119
|
+
writer.queue.join()
|
|
120
|
+
time.sleep(0.1)
|
|
121
|
+
|
|
122
|
+
def test_telemetry_event_context_exception(tmp_path):
|
|
123
|
+
settings = EventSettings(name=f"TEST_CTX_FAIL_{tmp_path.name}", logs_base_dir=str(tmp_path))
|
|
124
|
+
telemetry = TelemetryLog(settings)
|
|
125
|
+
|
|
126
|
+
with pytest.raises(ValueError, match="Test Error"):
|
|
127
|
+
with telemetry.event_context(general_note="Exception Test"):
|
|
128
|
+
raise ValueError("Test Error")
|
|
129
|
+
|
|
130
|
+
writer = telemetry._TelemetryLog__event_writer
|
|
131
|
+
writer.queue.join()
|
|
132
|
+
time.sleep(0.1)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|