loggerbuf 0.9.0__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.
- loggerbuf/__init__.py +33 -0
- loggerbuf/cli/__init__.py +0 -0
- loggerbuf/cli/console.py +453 -0
- loggerbuf/cli/handlers/__init__.py +0 -0
- loggerbuf/cli/handlers/decode.py +234 -0
- loggerbuf/cli/handlers/events.py +193 -0
- loggerbuf/cli/handlers/fields.py +119 -0
- loggerbuf/cli/handlers/protos.py +283 -0
- loggerbuf/cli/handlers/stress.py +272 -0
- loggerbuf/cli/utils/__init__.py +0 -0
- loggerbuf/cli/utils/registry.py +105 -0
- loggerbuf/cli/utils/schema_validator.py +138 -0
- loggerbuf/config.py +253 -0
- loggerbuf/data_logs/__init__.py +5 -0
- loggerbuf/data_logs/main_data_pb2.py +39 -0
- loggerbuf/data_logs/registry_pb2.py +40 -0
- loggerbuf/debugger.py +637 -0
- loggerbuf/queue_metrics.py +171 -0
- loggerbuf/schema_loader.py +70 -0
- loggerbuf/telemetry.py +391 -0
- loggerbuf-0.9.0.dist-info/METADATA +340 -0
- loggerbuf-0.9.0.dist-info/RECORD +25 -0
- loggerbuf-0.9.0.dist-info/WHEEL +5 -0
- loggerbuf-0.9.0.dist-info/entry_points.txt +2 -0
- loggerbuf-0.9.0.dist-info/top_level.txt +1 -0
loggerbuf/__init__.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from .debugger import DebuggerLog, LoggerSettings, LogDestination, LogLevel
|
|
2
|
+
from .telemetry import TelemetryLog, EventSettings
|
|
3
|
+
from .cli.handlers.decode import decode_file
|
|
4
|
+
|
|
5
|
+
def create_debugger(name="MAIN", stream=LogDestination.CONSOLE_AND_FILE_HISTORY, logs_base_dir="."):
|
|
6
|
+
"""
|
|
7
|
+
Helper function to get or create an operational Debugger instance.
|
|
8
|
+
|
|
9
|
+
Parameters
|
|
10
|
+
----------
|
|
11
|
+
name : str
|
|
12
|
+
Name of the debugger channel.
|
|
13
|
+
stream : LogDestination
|
|
14
|
+
Where to send logs (ONLY_CONSOLE, ONLY_FILE, CONSOLE_AND_FILE_HISTORY).
|
|
15
|
+
logs_base_dir : str
|
|
16
|
+
Base directory to store operational logs.
|
|
17
|
+
"""
|
|
18
|
+
settings = LoggerSettings(name=name, logs_base_dir=logs_base_dir, stream=stream)
|
|
19
|
+
return DebuggerLog(settings)
|
|
20
|
+
|
|
21
|
+
def create_telemetry(name="MAIN", logs_base_dir="."):
|
|
22
|
+
"""
|
|
23
|
+
Helper function to get or create a structured Telemetry instance.
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
name : str
|
|
28
|
+
Name of the telemetry channel.
|
|
29
|
+
logs_base_dir : str
|
|
30
|
+
Base directory to store binary telemetry files.
|
|
31
|
+
"""
|
|
32
|
+
settings = EventSettings(name=name, logs_base_dir=logs_base_dir)
|
|
33
|
+
return TelemetryLog(settings)
|
|
File without changes
|
loggerbuf/cli/console.py
ADDED
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import sys
|
|
3
|
+
import threading
|
|
4
|
+
from loggerbuf.cli.handlers import protos
|
|
5
|
+
from loggerbuf.cli.handlers import decode as decode_handler
|
|
6
|
+
from loggerbuf.cli.handlers import stress
|
|
7
|
+
from loggerbuf.cli.handlers import fields
|
|
8
|
+
from loggerbuf.cli.handlers import events
|
|
9
|
+
from ..config import ConfigManager
|
|
10
|
+
|
|
11
|
+
@click.group()
|
|
12
|
+
def cli():
|
|
13
|
+
"""LoggerBuf CLI - Asynchronous Telemetry Framework."""
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
@cli.group(name="protos")
|
|
17
|
+
def protos_group():
|
|
18
|
+
"""Manage Protocol Buffers schemas."""
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
@protos_group.command(name="init")
|
|
22
|
+
def protos_init_cmd():
|
|
23
|
+
"""Initializes the Protos structure and the Registry in the local project."""
|
|
24
|
+
try:
|
|
25
|
+
from ..config import ConfigManager
|
|
26
|
+
protos_dir = ConfigManager().get("PROTOS_DIR", "loggerbuf_schemas")
|
|
27
|
+
import os
|
|
28
|
+
if os.path.exists(protos_dir) and os.listdir(protos_dir):
|
|
29
|
+
click.secho(f"[{protos_dir}] is already initialized.", fg="green")
|
|
30
|
+
else:
|
|
31
|
+
protos.init()
|
|
32
|
+
click.secho(f"[{protos_dir}] created and initialized successfully.", fg="green")
|
|
33
|
+
except Exception as e:
|
|
34
|
+
click.secho(f"Error: {e}", fg="red")
|
|
35
|
+
sys.exit(1)
|
|
36
|
+
|
|
37
|
+
@cli.command()
|
|
38
|
+
@click.pass_context
|
|
39
|
+
def init(ctx):
|
|
40
|
+
"""Bootstraps a new LoggerBuf project in a single command."""
|
|
41
|
+
click.secho("Starting LoggerBuf project initialization...", fg="cyan")
|
|
42
|
+
try:
|
|
43
|
+
ctx.invoke(config_init)
|
|
44
|
+
ctx.invoke(protos_init_cmd)
|
|
45
|
+
ctx.invoke(build)
|
|
46
|
+
click.secho("Project initialized successfully! You are ready to log.", fg="green", bold=True)
|
|
47
|
+
except Exception as e:
|
|
48
|
+
click.secho(f"Initialization failed: {e}", fg="red")
|
|
49
|
+
sys.exit(1)
|
|
50
|
+
|
|
51
|
+
@cli.command(name="factory-reset")
|
|
52
|
+
@click.option('--hard', is_flag=True, help="Permanently delete setup files without creating a backup.")
|
|
53
|
+
def factory_reset(hard):
|
|
54
|
+
"""Resets the LoggerBuf configuration and schemas to ground zero."""
|
|
55
|
+
import os
|
|
56
|
+
import shutil
|
|
57
|
+
import datetime
|
|
58
|
+
from ..config import CONFIG_FILE, ConfigManager
|
|
59
|
+
|
|
60
|
+
click.secho("WARNING: This command will reset your entire LoggerBuf configuration and schemas.", fg="red", bold=True)
|
|
61
|
+
click.secho("Log data files will NOT be affected.\n", fg="yellow")
|
|
62
|
+
|
|
63
|
+
config_mgr = ConfigManager()
|
|
64
|
+
challenge_key = "LOGGING_BASE_DIR"
|
|
65
|
+
challenge_value = str(config_mgr.get(challenge_key))
|
|
66
|
+
|
|
67
|
+
click.secho(f"[Security] To confirm the reset, please type the current value of '{challenge_key}':", fg="cyan")
|
|
68
|
+
response = click.prompt("Value", type=str)
|
|
69
|
+
|
|
70
|
+
if response != challenge_value:
|
|
71
|
+
click.secho("Challenge failed. Factory reset aborted.", fg="red")
|
|
72
|
+
sys.exit(1)
|
|
73
|
+
|
|
74
|
+
protos_dir = config_mgr.get("PROTOS_DIR", "loggerbuf_schemas")
|
|
75
|
+
|
|
76
|
+
if not hard:
|
|
77
|
+
backup_dir = f"loggerbuf_backup_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
|
78
|
+
os.makedirs(backup_dir, exist_ok=True)
|
|
79
|
+
|
|
80
|
+
if os.path.exists(CONFIG_FILE):
|
|
81
|
+
shutil.copy(CONFIG_FILE, backup_dir)
|
|
82
|
+
|
|
83
|
+
if os.path.exists(protos_dir):
|
|
84
|
+
backup_protos = os.path.join(backup_dir, "schemas")
|
|
85
|
+
os.makedirs(backup_protos, exist_ok=True)
|
|
86
|
+
for f in os.listdir(protos_dir):
|
|
87
|
+
if f.endswith(".proto") or f.endswith(".json"):
|
|
88
|
+
shutil.copy(os.path.join(protos_dir, f), backup_protos)
|
|
89
|
+
|
|
90
|
+
click.secho(f"Backup created at: {backup_dir}/", fg="green")
|
|
91
|
+
|
|
92
|
+
# Delete
|
|
93
|
+
if os.path.exists(CONFIG_FILE):
|
|
94
|
+
os.remove(CONFIG_FILE)
|
|
95
|
+
if os.path.exists(protos_dir):
|
|
96
|
+
shutil.rmtree(protos_dir)
|
|
97
|
+
|
|
98
|
+
click.secho("Factory reset complete. The project has been wiped clean.", fg="green", bold=True)
|
|
99
|
+
|
|
100
|
+
@cli.command()
|
|
101
|
+
def build():
|
|
102
|
+
"""Rebuilds main_data.proto and compiles all classes."""
|
|
103
|
+
try:
|
|
104
|
+
protos.build()
|
|
105
|
+
click.secho("Build completed successfully.", fg="green")
|
|
106
|
+
except Exception as e:
|
|
107
|
+
click.secho(f"Error: {e}", fg="red")
|
|
108
|
+
sys.exit(1)
|
|
109
|
+
|
|
110
|
+
# Alias for 'build' inside the 'protos' group
|
|
111
|
+
@protos_group.command(name="build")
|
|
112
|
+
@click.pass_context
|
|
113
|
+
def protos_build(ctx):
|
|
114
|
+
"""Rebuilds main_data.proto and compiles all classes (alias for 'loggerbuf build')."""
|
|
115
|
+
ctx.invoke(build)
|
|
116
|
+
|
|
117
|
+
@cli.command()
|
|
118
|
+
@click.argument('name')
|
|
119
|
+
@click.option('--field', multiple=True, help="Fields in name:type format (e.g. --field age:int32)")
|
|
120
|
+
def create_event(name, field):
|
|
121
|
+
"""Creates a new .proto file for an event.
|
|
122
|
+
|
|
123
|
+
If --field is not provided, an interactive wizard will start.
|
|
124
|
+
"""
|
|
125
|
+
fields_list = []
|
|
126
|
+
if field:
|
|
127
|
+
for f in field:
|
|
128
|
+
parts = f.split(':')
|
|
129
|
+
if len(parts) != 2:
|
|
130
|
+
click.secho(f"Invalid field format: {f}. Use name:type", fg="red")
|
|
131
|
+
sys.exit(1)
|
|
132
|
+
fields_list.append((parts[0], parts[1]))
|
|
133
|
+
else:
|
|
134
|
+
click.secho(f"Wizard to create event '{name}'", fg="cyan")
|
|
135
|
+
while click.confirm("Add a field?"):
|
|
136
|
+
f_name = click.prompt("Field name")
|
|
137
|
+
f_type = click.prompt("Data type (e.g. string, int32, Enum_Name)")
|
|
138
|
+
fields_list.append((f_name, f_type))
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
protos.create_event(name, fields_list)
|
|
142
|
+
click.secho(f"Event {name} created. Now register it with 'loggerbuf register-event'.", fg="green")
|
|
143
|
+
except Exception as e:
|
|
144
|
+
click.secho(f"Error: {e}", fg="red")
|
|
145
|
+
sys.exit(1)
|
|
146
|
+
|
|
147
|
+
@cli.command()
|
|
148
|
+
@click.argument('field_name')
|
|
149
|
+
@click.argument('message_name')
|
|
150
|
+
@click.option('--file', help="Force the filename in case of collisions.")
|
|
151
|
+
def register_event(field_name, message_name, file):
|
|
152
|
+
"""Registers an event in main_data.proto and generates the build.
|
|
153
|
+
|
|
154
|
+
FIELD_NAME: Name of the variable (e.g. user_login)
|
|
155
|
+
MESSAGE_NAME: Name of the proto message (e.g. UserLogin)
|
|
156
|
+
"""
|
|
157
|
+
try:
|
|
158
|
+
protos.register_event(field_name, message_name, file)
|
|
159
|
+
click.secho(f"Event '{field_name}' registered and compiled successfully.", fg="green")
|
|
160
|
+
click.secho(
|
|
161
|
+
f"\n[TIP] Consider adding specific sub-classifications (EventTypes) and statuses (EventStatus) "
|
|
162
|
+
f"for your new event using 'loggerbuf event add-type' to enable deeper telemetry analytics.",
|
|
163
|
+
fg="cyan"
|
|
164
|
+
)
|
|
165
|
+
except Exception as e:
|
|
166
|
+
click.secho(f"Error: {e}", fg="red")
|
|
167
|
+
sys.exit(1)
|
|
168
|
+
|
|
169
|
+
@cli.command()
|
|
170
|
+
@click.argument('field_name')
|
|
171
|
+
def deprecate_event(field_name):
|
|
172
|
+
"""Marks an event as deprecated for safe backward compatibility."""
|
|
173
|
+
try:
|
|
174
|
+
protos.deprecate_event(field_name)
|
|
175
|
+
click.secho(f"Event '{field_name}' marked as deprecated successfully.", fg="yellow")
|
|
176
|
+
except Exception as e:
|
|
177
|
+
click.secho(f"Error: {e}", fg="red")
|
|
178
|
+
sys.exit(1)
|
|
179
|
+
|
|
180
|
+
@cli.command()
|
|
181
|
+
@click.argument('message_name')
|
|
182
|
+
@click.argument('field_name')
|
|
183
|
+
@click.argument('field_type')
|
|
184
|
+
@click.option('-f', '--file', help="Specify the .proto file name if the message exists in multiple files.")
|
|
185
|
+
def add_subfield(message_name, field_name, field_type, file):
|
|
186
|
+
"""Adds a new field to a specific proto message."""
|
|
187
|
+
try:
|
|
188
|
+
fields.add_subfield(message_name, field_name, field_type, file_name=file)
|
|
189
|
+
except Exception as e:
|
|
190
|
+
click.secho(f"Error: {e}", fg="red")
|
|
191
|
+
sys.exit(1)
|
|
192
|
+
|
|
193
|
+
@cli.command()
|
|
194
|
+
@click.argument('message_name')
|
|
195
|
+
@click.argument('field_name')
|
|
196
|
+
@click.option('-f', '--file', help="Specify the .proto file name if the message exists in multiple files.")
|
|
197
|
+
def deprecate_subfield(message_name, field_name, file):
|
|
198
|
+
"""Marks a specific field in a proto message as deprecated."""
|
|
199
|
+
try:
|
|
200
|
+
fields.deprecate_subfield(message_name, field_name, file_name=file)
|
|
201
|
+
except Exception as e:
|
|
202
|
+
click.secho(f"Error: {e}", fg="red")
|
|
203
|
+
sys.exit(1)
|
|
204
|
+
|
|
205
|
+
@cli.command()
|
|
206
|
+
@click.argument('input_file')
|
|
207
|
+
@click.option('--output', '-o', default=None, help="Output file (prints to stdout if not provided)")
|
|
208
|
+
@click.option('--format', type=click.Choice(['jsonl', 'pretty']), default='pretty', help="Output format")
|
|
209
|
+
@click.option('--stats', is_flag=True, help="Show summary statistics instead of full logs")
|
|
210
|
+
@click.option('--head', type=int, help="Output the first N events")
|
|
211
|
+
@click.option('--tail', type=int, help="Output the last N events")
|
|
212
|
+
@click.option('--verify', help="HMAC secret key to verify the integrity of the log file")
|
|
213
|
+
@click.option('--skip-integrity', is_flag=True, help="Skip HMAC integrity checks even if records contain signatures")
|
|
214
|
+
@click.option('--counters', is_flag=True, help="Decode file as CounterEvents instead of standard Events")
|
|
215
|
+
def decode(input_file, output, format, stats, head, tail, verify, skip_integrity, counters):
|
|
216
|
+
"""Decodes a LoggerBuf binary telemetry log into JSON/Stats."""
|
|
217
|
+
if head and tail:
|
|
218
|
+
click.secho("Error: Cannot use both --head and --tail simultaneously.", fg='red', err=True)
|
|
219
|
+
sys.exit(1)
|
|
220
|
+
|
|
221
|
+
decode_handler.run_decode(
|
|
222
|
+
input_file=input_file,
|
|
223
|
+
output_file=output,
|
|
224
|
+
format=format,
|
|
225
|
+
stats=stats,
|
|
226
|
+
head=head,
|
|
227
|
+
tail=tail,
|
|
228
|
+
verify_key=verify,
|
|
229
|
+
skip_integrity=skip_integrity,
|
|
230
|
+
is_counter=counters
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
@cli.command()
|
|
234
|
+
@click.argument('input_file')
|
|
235
|
+
@click.option('--grep', help="Filter logs by keyword (case-insensitive).")
|
|
236
|
+
@click.option('--head', type=int, help="Show only the first N logs.")
|
|
237
|
+
@click.option('--tail', type=int, help="Show only the last N logs.")
|
|
238
|
+
def decode_debug(input_file, grep, head, tail):
|
|
239
|
+
"""Explores historical JSON debug logs visually in the terminal."""
|
|
240
|
+
decode_handler.run_decode_debug(input_file, grep, head, tail)
|
|
241
|
+
|
|
242
|
+
@cli.group()
|
|
243
|
+
def event():
|
|
244
|
+
"""Manage Event types and statuses."""
|
|
245
|
+
pass
|
|
246
|
+
|
|
247
|
+
@cli.group()
|
|
248
|
+
def config():
|
|
249
|
+
"""Manage LoggerBuf global configurations (loggerbuf.json)."""
|
|
250
|
+
pass
|
|
251
|
+
|
|
252
|
+
@config.command(name="init")
|
|
253
|
+
def config_init():
|
|
254
|
+
"""Generates the default loggerbuf.json configuration file."""
|
|
255
|
+
from ..config import CONFIG_FILE, ConfigManager
|
|
256
|
+
try:
|
|
257
|
+
config_mgr = ConfigManager()
|
|
258
|
+
click.secho(f"Configuration file '{CONFIG_FILE}' is ready.", fg="green")
|
|
259
|
+
except Exception as e:
|
|
260
|
+
click.secho(f"Error: {e}", fg="red")
|
|
261
|
+
sys.exit(1)
|
|
262
|
+
|
|
263
|
+
@cli.group()
|
|
264
|
+
def counter():
|
|
265
|
+
"""Manage Counter types."""
|
|
266
|
+
pass
|
|
267
|
+
|
|
268
|
+
@counter.command(name="add-type")
|
|
269
|
+
@click.argument('name')
|
|
270
|
+
@click.option('--counters', default="", help="Comma separated list of counters (e.g. CLICK,VIEW)")
|
|
271
|
+
@click.option('--reserve', default=10, type=int, help="Number of extra indices to reserve for future counters.")
|
|
272
|
+
def add_counter_type(name, counters, reserve):
|
|
273
|
+
"""Adds a new counter block and optional counters."""
|
|
274
|
+
counter_list = [c.strip() for c in counters.split(",")] if counters else []
|
|
275
|
+
try:
|
|
276
|
+
events.add_counter_type(name, counter_list, reserve)
|
|
277
|
+
click.secho("\n[WARNING] You modified the .proto file.", fg="yellow")
|
|
278
|
+
click.secho("Run 'loggerbuf build' to compile it, and restart your application for changes to take effect.", fg="yellow")
|
|
279
|
+
except Exception as e:
|
|
280
|
+
click.secho(f"Error: {e}", fg="red")
|
|
281
|
+
sys.exit(1)
|
|
282
|
+
|
|
283
|
+
@event.command()
|
|
284
|
+
@click.argument('name')
|
|
285
|
+
@click.option('--statuses', default="", help="Comma separated list of statuses (e.g. ST1,ST2)")
|
|
286
|
+
@click.option('--reserve', default=10, type=int, help="Number of extra indices to reserve for future statuses.")
|
|
287
|
+
def add_type(name, statuses, reserve):
|
|
288
|
+
"""Adds a new event type and optional statuses."""
|
|
289
|
+
status_list = [s.strip() for s in statuses.split(",")] if statuses else []
|
|
290
|
+
try:
|
|
291
|
+
events.add_type(name, status_list, reserve)
|
|
292
|
+
click.secho("\n[WARNING] You modified the .proto file.", fg="yellow")
|
|
293
|
+
click.secho("Run 'loggerbuf build' to compile it, and restart your application for changes to take effect.", fg="yellow")
|
|
294
|
+
except Exception as e:
|
|
295
|
+
click.secho(f"Error: {e}", fg="red")
|
|
296
|
+
sys.exit(1)
|
|
297
|
+
|
|
298
|
+
@event.command()
|
|
299
|
+
@click.argument('type_name')
|
|
300
|
+
@click.argument('status_name')
|
|
301
|
+
def add_status(type_name, status_name):
|
|
302
|
+
"""Adds a new status to an existing event type."""
|
|
303
|
+
try:
|
|
304
|
+
events.add_status(type_name, status_name)
|
|
305
|
+
click.secho("\n[WARNING] You modified the .proto file.", fg="yellow")
|
|
306
|
+
click.secho("Run 'loggerbuf build' to compile it, and restart your application for changes to take effect.", fg="yellow")
|
|
307
|
+
except Exception as e:
|
|
308
|
+
click.secho(f"Error: {e}", fg="red")
|
|
309
|
+
sys.exit(1)
|
|
310
|
+
|
|
311
|
+
@event.command(name="list")
|
|
312
|
+
@click.argument('type_name', required=False)
|
|
313
|
+
def list_cmd(type_name):
|
|
314
|
+
"""Lists registered event types and statuses."""
|
|
315
|
+
try:
|
|
316
|
+
events.list_events(type_name)
|
|
317
|
+
except Exception as e:
|
|
318
|
+
click.secho(f"Error: {e}", fg="red")
|
|
319
|
+
sys.exit(1)
|
|
320
|
+
|
|
321
|
+
@config.command()
|
|
322
|
+
@click.argument('key')
|
|
323
|
+
@click.argument('value')
|
|
324
|
+
def set(key, value):
|
|
325
|
+
"""Sets a global configuration key to a specific value."""
|
|
326
|
+
try:
|
|
327
|
+
# Convert value to its proper type
|
|
328
|
+
if value.lower() in ("true", "false"):
|
|
329
|
+
value = value.lower() == "true"
|
|
330
|
+
elif value.isdigit():
|
|
331
|
+
value = int(value)
|
|
332
|
+
elif value.startswith("[") and value.endswith("]"):
|
|
333
|
+
import json
|
|
334
|
+
value = json.loads(value.replace("'", '"'))
|
|
335
|
+
|
|
336
|
+
config_mgr = ConfigManager()
|
|
337
|
+
config_mgr.set(key, value)
|
|
338
|
+
click.secho(f"Updated {key} = {value}", fg="green")
|
|
339
|
+
|
|
340
|
+
if key == "LOG_LEVEL":
|
|
341
|
+
click.secho("The ConfigWatcher will hot-reload this setting in ~5 seconds without restarting the app.", fg="green")
|
|
342
|
+
else:
|
|
343
|
+
click.secho(f"Warning: Modifying '{key}' requires manually restarting your application to take effect.", fg="yellow")
|
|
344
|
+
|
|
345
|
+
except Exception as e:
|
|
346
|
+
click.secho(f"Error: {e}", fg="red")
|
|
347
|
+
sys.exit(1)
|
|
348
|
+
|
|
349
|
+
@config.command()
|
|
350
|
+
@click.argument('key')
|
|
351
|
+
def reset(key):
|
|
352
|
+
"""Resets a configuration key to its global default."""
|
|
353
|
+
try:
|
|
354
|
+
config_mgr = ConfigManager()
|
|
355
|
+
# Verify it exists in our live config before trying to remove
|
|
356
|
+
if key not in config_mgr._config:
|
|
357
|
+
click.secho(f"Key '{key}' is not explicitly set, already using default.", fg="yellow")
|
|
358
|
+
sys.exit(0)
|
|
359
|
+
|
|
360
|
+
config_mgr.remove(key)
|
|
361
|
+
new_val = config_mgr.get(key)
|
|
362
|
+
click.secho(f"Reset {key} to default: {new_val}", fg="green")
|
|
363
|
+
except Exception as e:
|
|
364
|
+
click.secho(f"Error: {e}", fg="red")
|
|
365
|
+
sys.exit(1)
|
|
366
|
+
|
|
367
|
+
@config.command()
|
|
368
|
+
@click.argument('key')
|
|
369
|
+
def get(key):
|
|
370
|
+
"""Gets a configuration value, or group (all, logging, telemetry, metrics)."""
|
|
371
|
+
from ..config import DEFAULT_CONFIG, CONFIG_CHOICES
|
|
372
|
+
config_manager = ConfigManager()
|
|
373
|
+
|
|
374
|
+
group = key.lower()
|
|
375
|
+
if group in ('all', 'logging', 'telemetry', 'metrics'):
|
|
376
|
+
def print_group(prefix, title, color):
|
|
377
|
+
click.secho(f"\n--- {title} ---", fg=color, bold=True)
|
|
378
|
+
for k in DEFAULT_CONFIG.keys():
|
|
379
|
+
if k.startswith(prefix) or (prefix == 'LOG' and k == 'LOG_LEVEL'):
|
|
380
|
+
val = config_manager.get(k)
|
|
381
|
+
|
|
382
|
+
# If this key has limited choices, display them as hints
|
|
383
|
+
if k in CONFIG_CHOICES:
|
|
384
|
+
hints = f" [{' | '.join(map(str, CONFIG_CHOICES[k]))}]"
|
|
385
|
+
click.secho(f"{k}", fg="cyan", nl=False)
|
|
386
|
+
click.secho(hints, fg="yellow", nl=False)
|
|
387
|
+
click.secho(": ", fg="cyan", nl=False)
|
|
388
|
+
else:
|
|
389
|
+
click.secho(f"{k}: ", fg="cyan", nl=False)
|
|
390
|
+
click.echo(f"{val}")
|
|
391
|
+
|
|
392
|
+
if group in ('all', 'logging'):
|
|
393
|
+
print_group('LOG', 'LOGGING (DEBUGGER) SETTINGS', 'green')
|
|
394
|
+
if group in ('all', 'telemetry'):
|
|
395
|
+
print_group('EVENT_', 'TELEMETRY (EVENTS) SETTINGS', 'blue')
|
|
396
|
+
if group in ('all', 'metrics'):
|
|
397
|
+
print_group('METRIC_', 'METRICS (COUNTERS) SETTINGS', 'magenta')
|
|
398
|
+
if group == 'all':
|
|
399
|
+
print_group('STRESS_', 'STRESS TEST SETTINGS', 'yellow')
|
|
400
|
+
print_group('HMAC_', 'SECURITY SETTINGS', 'red')
|
|
401
|
+
|
|
402
|
+
click.echo("")
|
|
403
|
+
return
|
|
404
|
+
|
|
405
|
+
value = config_manager.get(key)
|
|
406
|
+
if value is not None:
|
|
407
|
+
click.echo("")
|
|
408
|
+
if key in CONFIG_CHOICES:
|
|
409
|
+
hints = f" [{' | '.join(map(str, CONFIG_CHOICES[key]))}]"
|
|
410
|
+
click.secho(f"Configuration for ", fg="white", nl=False)
|
|
411
|
+
click.secho(f"{key}", fg="cyan", bold=True, nl=False)
|
|
412
|
+
click.secho(hints, fg="yellow", nl=False)
|
|
413
|
+
click.secho(":", fg="white")
|
|
414
|
+
click.secho(f" Current value: ", fg="white", nl=False)
|
|
415
|
+
click.secho(f"{value}", fg="green", bold=True)
|
|
416
|
+
else:
|
|
417
|
+
click.secho(f"Configuration for ", fg="white", nl=False)
|
|
418
|
+
click.secho(f"{key}", fg="cyan", bold=True, nl=False)
|
|
419
|
+
click.secho(":", fg="white")
|
|
420
|
+
click.secho(f" Current value: ", fg="white", nl=False)
|
|
421
|
+
click.secho(f"{value}", fg="green", bold=True)
|
|
422
|
+
click.echo("")
|
|
423
|
+
else:
|
|
424
|
+
click.secho(f"Key '{key}' not found in config or defaults.", fg="red")
|
|
425
|
+
|
|
426
|
+
@cli.command()
|
|
427
|
+
@click.option('--threads', type=int, help="Number of concurrent threads. Overrides config.")
|
|
428
|
+
@click.option('--writes', type=int, help="Total number of writes across all threads. Overrides config.")
|
|
429
|
+
@click.option('--duration', type=int, help="Target duration in seconds to pace the traffic. 0 for burst. Overrides config.")
|
|
430
|
+
@click.option('--queue-size', type=int, help="Max queue capacity. Overrides config.")
|
|
431
|
+
@click.option('--strategy', type=click.Choice(['lossless', 'lossy'], case_sensitive=False), help="Queue strategy. Overrides config.")
|
|
432
|
+
@click.option('--keep-logs', is_flag=True, default=False, help="Do not delete the stress test log directory at the end.")
|
|
433
|
+
def stress_test(threads, writes, duration, queue_size, strategy, keep_logs):
|
|
434
|
+
"""Runs a performance stress test on LoggerBuf with resource monitoring."""
|
|
435
|
+
config_manager = ConfigManager()
|
|
436
|
+
|
|
437
|
+
threads = threads if threads is not None else config_manager.get('STRESS_TEST_THREADS', 10)
|
|
438
|
+
writes = writes if writes is not None else config_manager.get('STRESS_TEST_WRITES', 10000)
|
|
439
|
+
duration = duration if duration is not None else config_manager.get('STRESS_TEST_DURATION', 0)
|
|
440
|
+
queue_size = queue_size if queue_size is not None else config_manager.get('LOGGING_QUEUE_MAX_SIZE')
|
|
441
|
+
strategy = strategy if strategy is not None else config_manager.get('LOGGING_QUEUE_STRATEGY')
|
|
442
|
+
|
|
443
|
+
stress.run_stress_test(
|
|
444
|
+
num_threads=threads,
|
|
445
|
+
total_writes=writes,
|
|
446
|
+
duration=duration,
|
|
447
|
+
queue_size=queue_size,
|
|
448
|
+
strategy=strategy.upper(),
|
|
449
|
+
keep_logs=keep_logs
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
if __name__ == '__main__':
|
|
453
|
+
cli()
|
|
File without changes
|