From 572b0075a6908b502b96125479827a0cc834c57f Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Sat, 27 Apr 2024 12:07:47 +0100 Subject: [PATCH 1/3] Add "logfire info" and issue templates --- .github/ISSUE_TEMPLATE/config.yml | 11 +++ .github/ISSUE_TEMPLATE/platform-bug.yml | 19 +++++ .github/ISSUE_TEMPLATE/sdk-bug.yml | 36 +++++++++ README.md | 2 +- logfire/_internal/cli.py | 102 ++++++++++++++++++------ 5 files changed, 143 insertions(+), 27 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/platform-bug.yml create mode 100644 .github/ISSUE_TEMPLATE/sdk-bug.yml diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..43ae1ab9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: true +contact_links: + - name: 💬 Join Slack + url: 'https://docs.pydantic.dev/logfire/help/#slack' + about: Join the Pydantic Logfire Slack to ask questions, get help and chat about Logfire + - name: 🤔 Ask a Question on GitHub Discussions + url: 'https://github.com/pydantic/pydantic/discussions/new?category=question' + about: Ask a question about how to use Logfire using github discussions + - name: 📧 Email Us + url: 'mailto:engineering@pydantic.dev' + about: To contact us privately, please email engineering@pydantic.dev diff --git a/.github/ISSUE_TEMPLATE/platform-bug.yml b/.github/ISSUE_TEMPLATE/platform-bug.yml new file mode 100644 index 00000000..44827dbd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/platform-bug.yml @@ -0,0 +1,19 @@ +name: 🐛 Report a Platform Bug +description: Report a an issue with the rest of Logfire +labels: [Platform Bug] + +body: + - type: markdown + attributes: + value: Thank you for contributing to Logfire! ✊ + + - type: textarea + id: description + attributes: + label: Description + description: | + Please explain what you're seeing and what you would expect to see. + + Please provide as much detail as possible to make understanding and solving your problem as quick as possible. 🙏 + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/sdk-bug.yml b/.github/ISSUE_TEMPLATE/sdk-bug.yml new file mode 100644 index 00000000..1c67ff75 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/sdk-bug.yml @@ -0,0 +1,36 @@ +name: 🐛 Report an SDK Bug +description: Report a an issue with the Logfire SDK +labels: [SDK Bug] + +body: + - type: markdown + attributes: + value: Thank you for contributing to Logfire! ✊ + + - type: textarea + id: description + attributes: + label: Description + description: | + Please explain what you're seeing and what you would expect to see. + + Please provide as much detail as possible to make understanding and solving your problem as quick as possible. 🙏 + validations: + required: true + + - type: textarea + id: version + attributes: + label: Python, Logfire & OS Versions, related packages + description: | + Which version of Python & Logfire are you using, with which Operating System and with which OpenTelemetry packages? + + Don't worry if you can't run this command and don't have this information, we'll help you if we can. + + Please run the following command and copy the output below: + + ```bash + logfire info + ``` + + render: TOML diff --git a/README.md b/README.md index 328be36d..44b35fd0 100644 --- a/README.md +++ b/README.md @@ -82,4 +82,4 @@ We'd love anyone interested to contribute to the Logfire SDK and documentation, ## Reporting a Security Vulnerability -See our [security policy](https://github.com/pydantic/.github). +See our [security policy](https://github.com/pydantic/logfire/security). diff --git a/logfire/_internal/cli.py b/logfire/_internal/cli.py index 8a177ea6..275eea3f 100644 --- a/logfire/_internal/cli.py +++ b/logfire/_internal/cli.py @@ -59,7 +59,7 @@ def version_callback() -> None: # TODO(Marcelo): Needs to be updated to reflect `logfire auth`. def parse_whoami(args: argparse.Namespace) -> None: - """Get your dashboard url and project name.""" + """Show user authenticated username and the URL to your Logfire project.""" data_dir = Path(args.data_dir) current_user = LogfireCredentials.get_current_user(session=args._session, logfire_api_url=args.logfire_url) if current_user is None: @@ -77,7 +77,7 @@ def parse_whoami(args: argparse.Namespace) -> None: def parse_clean(args: argparse.Namespace) -> None: - """Clean Logfire data.""" + """Remove the contents of the Logfire data directory.""" if args.logs and LOGFIRE_LOG_FILE.exists(): LOGFIRE_LOG_FILE.unlink() @@ -96,7 +96,7 @@ def parse_clean(args: argparse.Namespace) -> None: # TODO(Marcelo): Add tests for this command. def parse_backfill(args: argparse.Namespace) -> None: # pragma: no cover - """Bulk load Logfire data.""" + """Bulk upload data to Logfire.""" data_dir = Path(args.data_dir) credentials = LogfireCredentials.load_creds_file(data_dir) if credentials is None: @@ -181,7 +181,7 @@ def reader() -> Iterator[bytes]: def parse_inspect(args: argparse.Namespace) -> None: - """Inspect installed packages and recommend the opentelemetry package that can be used with it.""" + """Inspect installed packages and recommend packages that might be useful.""" console = Console(file=sys.stderr) table = Table() table.add_column('Package') @@ -231,7 +231,7 @@ def parse_inspect(args: argparse.Namespace) -> None: def parse_auth(args: argparse.Namespace) -> None: """Authenticate with Logfire. - It will authenticate you with Logfire, and store the credentials. + This will authenticate your machine with Logfire and store the credentials. """ console = Console(file=sys.stderr) logfire_url = cast(str, args.logfire_url) @@ -340,6 +340,54 @@ def parse_use_project(args: argparse.Namespace) -> None: console.print(f'Project configured successfully. You will be able to view it at: {credentials.project_url}') +def parse_info(_args: argparse.Namespace) -> None: + """Show versions of logfire, OS and related packages.""" + import importlib.metadata as importlib_metadata + + from rich.syntax import Syntax + + # get data about packages that are closely related to logfire + package_names = { + # use by otel to send data + 'requests': 1, + # custom integration + 'pydantic': 2, + # otel integration is customed + 'fastapi': 3, + # custom integration + 'openai': 4, + # dependencies of otel + 'protobuf': 5, + # dependencies + 'rich': 6, + # dependencies + 'typing-extensions': 7, + # dependencies + 'tomli': 8, + } + otel_index = max(package_names.values(), default=0) + 1 + related_packages: list[tuple[int, str, str]] = [] + + for dist in importlib_metadata.distributions(): + name = dist.metadata['Name'] + index = package_names.get(name) + if index is not None: + related_packages.append((index, name, dist.version)) + if name.startswith('opentelemetry'): + related_packages.append((otel_index, name, dist.version)) + + toml_lines = ( + f'logfire="{VERSION}"', + f'platform="{platform.platform()}"', + f'python="{sys.version}"', + '[related_packages]', + *(f'{name}="{version}"' for _, name, version in sorted(related_packages)), + ) + console = Console(file=sys.stderr) + # use background_color='default' to avoid rich's annoying background color that messes up copy-pasting + console.print(Syntax('\n'.join(toml_lines), 'toml', background_color='default', word_wrap=True)) + + def _main(args: list[str] | None = None) -> None: parser = argparse.ArgumentParser( prog='logfire', @@ -354,32 +402,30 @@ def _main(args: list[str] | None = None) -> None: subparsers = parser.add_subparsers(title='commands', metavar='') # Note(DavidM): Let's try to keep the commands listed in alphabetical order if we can - cmd_auth = subparsers.add_parser('auth', help='authenticate with Logfire', description=parse_auth.__doc__) + cmd_auth = subparsers.add_parser('auth', help=parse_auth.__doc__.split('\n', 1)[0], description=parse_auth.__doc__) cmd_auth.set_defaults(func=parse_auth) - cmd_backfill = subparsers.add_parser('backfill', help='bulk ingest backfill data') + cmd_backfill = subparsers.add_parser('backfill', help=parse_backfill.__doc__) + cmd_backfill.set_defaults(func=parse_backfill) cmd_backfill.add_argument('--data-dir', default='.logfire') cmd_backfill.add_argument('--file', default='logfire_spans.bin') - cmd_backfill.set_defaults(func=parse_backfill) - cmd_clean = subparsers.add_parser('clean', help='remove the contents of the Logfire data directory') + cmd_clean = subparsers.add_parser('clean', help=parse_clean.__doc__) + cmd_clean.set_defaults(func=parse_clean) cmd_clean.add_argument('--data-dir', default='.logfire') cmd_clean.add_argument('--logs', action='store_true', default=False, help='remove the Logfire logs') - cmd_clean.set_defaults(func=parse_clean) - cmd_inspect = subparsers.add_parser( - 'inspect', - help="suggest OpenTelemetry instrumentations based on your environment's installed packages", - ) + cmd_inspect = subparsers.add_parser('inspect', help=parse_inspect.__doc__) cmd_inspect.set_defaults(func=parse_inspect) - cmd_whoami = subparsers.add_parser('whoami', help='display the URL to your Logfire project') - cmd_whoami.add_argument('--data-dir', default='.logfire') + cmd_whoami = subparsers.add_parser('whoami', help=parse_whoami.__doc__) cmd_whoami.set_defaults(func=parse_whoami) + cmd_whoami.add_argument('--data-dir', default='.logfire') - cmd_projects = subparsers.add_parser('projects', help='project management for Logfire') + cmd_projects = subparsers.add_parser('projects', help='Project management for Logfire.') cmd_projects.set_defaults(func=lambda _: cmd_projects.print_help()) # type: ignore projects_subparsers = cmd_projects.add_subparsers() + cmd_projects_list = projects_subparsers.add_parser('list', help='list projects') cmd_projects_list.set_defaults(func=parse_list_projects) @@ -398,6 +444,9 @@ def _main(args: list[str] | None = None) -> None: cmd_projects_use.add_argument('--data-dir', default='.logfire') cmd_projects_use.set_defaults(func=parse_use_project) + cmd_info = subparsers.add_parser('info', help=parse_info.__doc__) + cmd_info.set_defaults(func=parse_info) + namespace = parser.parse_args(args) trace.set_tracer_provider(tracer_provider=SDKTracerProvider()) @@ -407,15 +456,16 @@ def log_trace_id(response: requests.Response, context: ContextCarrier, *args: An logger.debug('context=%s url=%s', context, response.url) with tracer.start_as_current_span('logfire._internal.cli'): - with requests.Session() as session: - context = get_context() - session.hooks = {'response': functools.partial(log_trace_id, context=context)} - session.headers.update(context) - namespace._session = session - - if namespace.version: - version_callback() - else: + if namespace.version: + version_callback() + elif namespace.func == parse_info: + namespace.func(namespace) + else: + with requests.Session() as session: + context = get_context() + session.hooks = {'response': functools.partial(log_trace_id, context=context)} + session.headers.update(context) + namespace._session = session namespace.func(namespace) From 1b3c45c5ba60c621cae2fadc796e7f1d992565b7 Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Sat, 27 Apr 2024 12:15:10 +0100 Subject: [PATCH 2/3] add tests --- logfire/_internal/cli.py | 2 +- tests/test_cli.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/logfire/_internal/cli.py b/logfire/_internal/cli.py index 275eea3f..8fc6a95b 100644 --- a/logfire/_internal/cli.py +++ b/logfire/_internal/cli.py @@ -86,7 +86,7 @@ def parse_clean(args: argparse.Namespace) -> None: sys.stderr.write(f'No Logfire data found in {data_dir.resolve()}\n') sys.exit(1) - confirm = input(f'The folder {data_dir.resolve()} will be deleted. Are you sure? [N/y]') + confirm = input(f'The folder {data_dir.resolve()} will be deleted. Are you sure? [N/y] ') if confirm.lower() in ('yes', 'y'): shutil.rmtree(data_dir) sys.stderr.write('Cleaned Logfire data.\n') diff --git a/tests/test_cli.py b/tests/test_cli.py index 2ed5425a..e35121ce 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1068,3 +1068,10 @@ def test_projects_use_write_token_error(tmp_dir_cwd: Path, default_credentials: with pytest.raises(LogfireConfigError, match='Error creating project write token.'): main(['projects', 'use', 'myproject', '--org', 'fake_org']) + + +def test_info(capsys: pytest.CaptureFixture[str]) -> None: + main(['info']) + output = capsys.readouterr().err.strip() + assert output.startswith('logfire="') + assert '[related_packages]' in output From e715f7412307268ed8f8805db3636f792e4372b2 Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Sat, 27 Apr 2024 12:18:19 +0100 Subject: [PATCH 3/3] fix docs link in cli --- logfire/_internal/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logfire/_internal/cli.py b/logfire/_internal/cli.py index 8fc6a95b..96470ddc 100644 --- a/logfire/_internal/cli.py +++ b/logfire/_internal/cli.py @@ -392,7 +392,7 @@ def _main(args: list[str] | None = None) -> None: parser = argparse.ArgumentParser( prog='logfire', description='The CLI for Pydantic Logfire.', - epilog='See https://docs.pydantic.dev/logfire/guide/cli/ for more detailed documentation.', + epilog='See https://docs.pydantic.dev/logfire/reference/cli/ for more detailed documentation.', ) parser.add_argument('--version', action='store_true', help='show the version and exit')