Skip to content

Commit d554140

Browse files
authored
Merge pull request #1 from amjith/amjith/auto-instrument
Add auto-instrumentation for Click.
2 parents b251d1c + 89f53cb commit d554140

2 files changed

Lines changed: 49 additions & 20 deletions

File tree

cli_telemetry/telemetry.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ def init_telemetry(service_name: str, db_path: Optional[str] = None, user_id_fil
8181
if _initialized:
8282
return
8383

84+
# monkeypatch click if allowed
85+
if os.environ.get("CLI_TELEMETRY_DISABLE_CLICK_PATCH"):
86+
_monkeypatch_click()
87+
8488
# determine base path under XDG_DATA_HOME
8589
xdg = os.environ.get("XDG_DATA_HOME", os.path.expanduser("~/.local/share"))
8690
base = os.path.join(xdg, "cli-telemetry", service_name)
@@ -266,3 +270,39 @@ def end_session() -> None:
266270
_conn.close()
267271
except Exception:
268272
pass
273+
274+
275+
def _monkeypatch_click():
276+
"""Monkeypatch click.Command and click.Group to auto-wrap in telemetry spans."""
277+
import os
278+
279+
if os.environ.get("CLI_TELEMETRY_DISABLE_CLICK_PATCH") == "1":
280+
return # User has opted out of monkeypatching
281+
282+
try:
283+
import click
284+
285+
def telemetry_wrapper(original_invoke):
286+
def invoke_with_span(self, ctx):
287+
# Assumes init_telemetry() already called by app
288+
with Span(self.name, attributes={"cli.command": ctx.command_path}) as span:
289+
try:
290+
for param in self.params:
291+
if param.name in ctx.params:
292+
add_tag(f"args.{param.name}", ctx.params[param.name])
293+
except Exception:
294+
pass
295+
return original_invoke(self, ctx)
296+
return invoke_with_span
297+
298+
# Avoid double-patching
299+
if not getattr(click.Command, "_telemetry_patched", False):
300+
click.Command.invoke = telemetry_wrapper(click.Command.invoke)
301+
click.Command._telemetry_patched = True
302+
303+
if not getattr(click.Group, "_telemetry_patched", False):
304+
click.Group.invoke = telemetry_wrapper(click.Group.invoke)
305+
click.Group._telemetry_patched = True
306+
307+
except ImportError:
308+
pass # No click? No telemetry sadness.

examples/click_ex.py

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,47 @@
11
from time import sleep
22
import click
3-
from cli_telemetry.telemetry import start_session, end_session, profile, add_tag, profile_block
43

4+
# import this first so it can patch click before any commands are defined
5+
import cli_telemetry.telemetry as telemetry
6+
from cli_telemetry.telemetry import profile, add_tag, profile_block
57

6-
@click.group()
7-
@click.pass_context
8-
def cli(ctx):
9-
"""
10-
Example CLI with telemetry instrumentation.
11-
"""
12-
# Start the root span for this invocation
13-
cmd = ctx.invoked_subcommand or "cli"
14-
start_session(command_name=cmd, service_name="example-cli")
15-
# Ensure we end the span when the CLI exits
16-
ctx.call_on_close(end_session)
8+
telemetry.init_telemetry("example-cli")
179

10+
@click.group()
11+
def cli():
12+
"""Example CLI with telemetry instrumentation."""
13+
# no manual start_session() or call_on_close() needed any more
14+
pass
1815

1916
@cli.command()
2017
@click.argument("message")
21-
@profile
2218
def echo(message):
2319
"""Prints the message as-is."""
2420
# Tag the argument so it shows up on this span
2521
add_tag("args.message", message)
2622
click.echo(message)
2723

28-
2924
@cli.command()
3025
@click.argument("message")
3126
@click.option("--times", "-n", default=1, show_default=True, help="How many times to shout")
32-
@profile
3327
def shout(message, times):
3428
"""Prints the message uppercased with exclamation."""
3529
add_tag("args.message", message)
3630
add_tag("args.times", times)
3731
for _ in range(times):
3832
click.echo(f"{message.upper()}!")
3933

40-
4134
@cli.command()
42-
@profile
4335
def work():
4436
"""Simulate some nested work using a profile_block."""
4537
add_tag("phase", "start_work")
4638
with profile_block("step_1", tags={"step": 1}):
4739
click.echo("step1")
4840
sleep(0.1)
49-
pass
5041
with profile_block("step_2", tags={"step": 2}):
5142
click.echo("step2")
5243
sleep(0.2)
53-
pass
5444
click.echo("Work done!")
5545

56-
5746
if __name__ == "__main__":
5847
cli()

0 commit comments

Comments
 (0)