Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add logfire info and issue templates #22

Merged
merged 3 commits into from
Apr 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -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:[email protected]'
about: To contact us privately, please email [email protected]
19 changes: 19 additions & 0 deletions .github/ISSUE_TEMPLATE/platform-bug.yml
Original file line number Diff line number Diff line change
@@ -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
36 changes: 36 additions & 0 deletions .github/ISSUE_TEMPLATE/sdk-bug.yml
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
106 changes: 78 additions & 28 deletions logfire/_internal/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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()

Expand All @@ -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')
Expand All @@ -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:
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -340,11 +340,59 @@ 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',
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')
Expand All @@ -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)

Expand All @@ -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())
Expand All @@ -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)


Expand Down
7 changes: 7 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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