Skip to content

Commit c0efeb0

Browse files
authored
Print project link eagerly from credentials file if possible (#1393)
1 parent cfd7a5f commit c0efeb0

File tree

2 files changed

+85
-18
lines changed

2 files changed

+85
-18
lines changed

logfire/_internal/config.py

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -897,26 +897,47 @@ def add_span_processor(span_processor: SpanProcessor) -> None:
897897
metric_readers = list(self.metrics.additional_readers)
898898

899899
if self.send_to_logfire:
900-
credentials: LogfireCredentials | None = None
901900
show_project_link: bool = self.console and self.console.show_project_link or False
902901

903-
# try loading credentials (and thus token) from file if a token is not already available
904-
# this takes the lowest priority, behind the token passed to `configure` and the environment variable
905-
if not self.token:
902+
# Try loading credentials from a file.
903+
# If that works, we can use it to immediately print the project link.
904+
try:
906905
credentials = LogfireCredentials.load_creds_file(self.data_dir)
907-
908-
# if we still don't have a token, try initializing a new project and writing a new creds file
906+
except Exception:
907+
# If we have a token configured by other means, e.g. the env, no need to worry about the creds file.
908+
if not self.token:
909+
raise
910+
credentials = None
911+
912+
if not self.token and self.send_to_logfire is True and credentials is None:
913+
# If we don't have a token or credentials from a file,
914+
# try initializing a new project and writing a new creds file.
909915
# note, we only do this if `send_to_logfire` is explicitly `True`, not 'if-token-present'
910-
if self.send_to_logfire is True and credentials is None:
911-
client = LogfireClient.from_url(self.advanced.base_url)
912-
credentials = LogfireCredentials.initialize_project(client=client)
913-
credentials.write_creds_file(self.data_dir)
916+
client = LogfireClient.from_url(self.advanced.base_url)
917+
credentials = LogfireCredentials.initialize_project(client=client)
918+
credentials.write_creds_file(self.data_dir)
914919

915-
if credentials is not None:
916-
self.token = credentials.token
917-
self.advanced.base_url = self.advanced.base_url or credentials.logfire_api_url
920+
if credentials is not None:
921+
# Get token and base_url from credentials if not already set.
922+
# This means that e.g. a token in an env var takes priority over a token in a creds file.
923+
self.token = self.token or credentials.token
924+
self.advanced.base_url = self.advanced.base_url or credentials.logfire_api_url
918925

919926
if self.token:
927+
if credentials and self.token == credentials.token and show_project_link:
928+
# The creds file contains the project link, so we can display it immediately.
929+
# We do this if the token comes from the creds file or if it was explicitly configured
930+
# and happens to match the creds file anyway.
931+
credentials.print_token_summary()
932+
# Don't print it again in check_token below.
933+
show_project_link = False
934+
935+
# Regardless of where the token comes from, check that it's valid.
936+
# Even if it comes from a creds file, it could be revoked or expired.
937+
# If it's valid and we haven't already printed a project link, print it here.
938+
# This may happen some time later in a background thread which can be annoying,
939+
# hence we try to print it eagerly above.
940+
# But we only have the link if we have a creds file, otherwise we only know the token at this point.
920941

921942
def check_token():
922943
assert self.token is not None

tests/test_configure.py

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1161,12 +1161,15 @@ def test_initialize_project_create_project(tmp_dir_cwd: Path, tmp_path: Path, ca
11611161
)
11621162

11631163
logfire.configure(send_to_logfire=True)
1164+
assert capsys.readouterr().err == 'Logfire project URL: fake_project_url\n'
11641165

1165-
for request in request_mocker.request_history[:-1]:
1166+
for request in request_mocker.request_history[:5]:
11661167
assert request.headers['Authorization'] == 'fake_user_token'
1168+
assert len(request_mocker.request_history) in (5, 6)
11671169

11681170
# we check that fake_token is valid now when we configure the project
11691171
wait_for_check_token_thread()
1172+
assert len(request_mocker.request_history) == 6
11701173
assert request_mocker.request_history[-1].headers['Authorization'] == 'fake_token'
11711174

11721175
assert request_mocker.request_history[2].json() == create_existing_project_request_json
@@ -1203,7 +1206,6 @@ def test_initialize_project_create_project(tmp_dir_cwd: Path, tmp_path: Path, ca
12031206
'Project initialized successfully. You will be able to view it at: fake_project_url\nPress Enter to continue',
12041207
),
12051208
]
1206-
assert capsys.readouterr().err == 'Logfire project URL: fake_project_url\n'
12071209

12081210
assert json.loads((tmp_dir_cwd / '.logfire/logfire_credentials.json').read_text()) == {
12091211
**create_project_response['json'],
@@ -1399,9 +1401,9 @@ def test_send_to_logfire_if_token_present_in_logfire_dir(tmp_path: Path, capsys:
13991401
json={'project_name': 'myproject', 'project_url': 'https://logfire-us.pydantic.dev'},
14001402
)
14011403
configure(send_to_logfire='if-token-present', data_dir=tmp_path)
1404+
assert capsys.readouterr().err == 'Logfire project URL: https://logfire-us.pydantic.dev\n'
14021405
wait_for_check_token_thread()
14031406
assert len(request_mocker.request_history) == 1
1404-
assert capsys.readouterr().err == 'Logfire project URL: https://logfire-us.pydantic.dev\n'
14051407

14061408

14071409
def test_configure_unknown_token_region(capsys: pytest.CaptureFixture[str]) -> None:
@@ -1422,7 +1424,51 @@ def test_load_creds_file_invalid_json_content(tmp_path: Path):
14221424
creds_file.write_text('invalid-data')
14231425

14241426
with pytest.raises(LogfireConfigError, match='Invalid credentials file:'):
1425-
LogfireCredentials.load_creds_file(creds_dir=tmp_path)
1427+
logfire.configure(data_dir=tmp_path, send_to_logfire=True)
1428+
1429+
1430+
def test_load_creds_file_invalid_json_content_with_token_present(tmp_path: Path, capsys: pytest.CaptureFixture[str]):
1431+
creds_file = tmp_path / 'logfire_credentials.json'
1432+
creds_file.write_text('invalid-data')
1433+
1434+
with patch.dict(os.environ, {'LOGFIRE_TOKEN': 'fake_token'}), requests_mock.Mocker() as request_mocker:
1435+
request_mocker.get(
1436+
'https://logfire-us.pydantic.dev/v1/info',
1437+
json={'project_name': 'myproject', 'project_url': 'fake_project_url'},
1438+
)
1439+
logfire.configure(data_dir=tmp_path, send_to_logfire=True)
1440+
wait_for_check_token_thread()
1441+
assert len(request_mocker.request_history) == 1
1442+
assert request_mocker.request_history[0].headers['Authorization'] == 'fake_token'
1443+
assert capsys.readouterr().err == 'Logfire project URL: fake_project_url\n'
1444+
1445+
1446+
def test_load_creds_file_with_token_different_from_env(tmp_path: Path, capsys: pytest.CaptureFixture[str]):
1447+
creds_file = tmp_path / 'logfire_credentials.json'
1448+
creds_file.write_text(
1449+
"""
1450+
{
1451+
"token": "foobar",
1452+
"project_name": "myproject",
1453+
"project_url": "https://logfire-us.pydantic.dev",
1454+
"logfire_api_url": "https://logfire-us.pydantic.dev"
1455+
}
1456+
"""
1457+
)
1458+
1459+
with patch.dict(os.environ, {'LOGFIRE_TOKEN': 'fake_token'}), requests_mock.Mocker() as request_mocker:
1460+
request_mocker.get(
1461+
'https://logfire-us.pydantic.dev/v1/info',
1462+
json={'project_name': 'myproject', 'project_url': 'fake_project_url'},
1463+
)
1464+
logfire.configure(data_dir=tmp_path, send_to_logfire=True)
1465+
# not 'foobar' from the creds file. The token in the env var takes precedence.
1466+
assert logfire.DEFAULT_LOGFIRE_INSTANCE.config.token == 'fake_token'
1467+
1468+
wait_for_check_token_thread()
1469+
assert len(request_mocker.request_history) == 1
1470+
assert request_mocker.request_history[0].headers['Authorization'] == 'fake_token'
1471+
assert capsys.readouterr().err == 'Logfire project URL: fake_project_url\n'
14261472

14271473

14281474
def test_load_creds_file_legacy_key(tmp_path: Path):
@@ -1447,7 +1493,7 @@ def test_load_creds_file_invalid_key(tmp_path: Path):
14471493
creds_file.write_text('{"test": "test"}')
14481494

14491495
with pytest.raises(LogfireConfigError, match='Invalid credentials file:'):
1450-
LogfireCredentials.load_creds_file(creds_dir=tmp_path)
1496+
logfire.configure(data_dir=tmp_path, send_to_logfire=True)
14511497

14521498

14531499
def test_initialize_credentials_from_token_unreachable():

0 commit comments

Comments
 (0)