loki-reader-core 0.2.3__tar.gz → 0.2.4__tar.gz

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.
Files changed (21) hide show
  1. {loki_reader_core-0.2.3 → loki_reader_core-0.2.4}/PKG-INFO +1 -1
  2. {loki_reader_core-0.2.3 → loki_reader_core-0.2.4}/pyproject.toml +1 -1
  3. {loki_reader_core-0.2.3 → loki_reader_core-0.2.4}/src/loki_reader_core/__init__.py +1 -1
  4. {loki_reader_core-0.2.3 → loki_reader_core-0.2.4}/src/loki_reader_core/client.py +21 -13
  5. {loki_reader_core-0.2.3 → loki_reader_core-0.2.4}/tests/test_client.py +32 -16
  6. {loki_reader_core-0.2.3 → loki_reader_core-0.2.4}/.gitignore +0 -0
  7. {loki_reader_core-0.2.3 → loki_reader_core-0.2.4}/README.md +0 -0
  8. {loki_reader_core-0.2.3 → loki_reader_core-0.2.4}/build-publish.sh +0 -0
  9. {loki_reader_core-0.2.3 → loki_reader_core-0.2.4}/dev-requirements.txt +0 -0
  10. {loki_reader_core-0.2.3 → loki_reader_core-0.2.4}/src/loki_reader_core/exceptions.py +0 -0
  11. {loki_reader_core-0.2.3 → loki_reader_core-0.2.4}/src/loki_reader_core/models/__init__.py +0 -0
  12. {loki_reader_core-0.2.3 → loki_reader_core-0.2.4}/src/loki_reader_core/models/log_entry.py +0 -0
  13. {loki_reader_core-0.2.3 → loki_reader_core-0.2.4}/src/loki_reader_core/models/log_stream.py +0 -0
  14. {loki_reader_core-0.2.3 → loki_reader_core-0.2.4}/src/loki_reader_core/models/metric_sample.py +0 -0
  15. {loki_reader_core-0.2.3 → loki_reader_core-0.2.4}/src/loki_reader_core/models/metric_series.py +0 -0
  16. {loki_reader_core-0.2.3 → loki_reader_core-0.2.4}/src/loki_reader_core/models/query_result.py +0 -0
  17. {loki_reader_core-0.2.3 → loki_reader_core-0.2.4}/src/loki_reader_core/models/query_stats.py +0 -0
  18. {loki_reader_core-0.2.3 → loki_reader_core-0.2.4}/src/loki_reader_core/utils.py +0 -0
  19. {loki_reader_core-0.2.3 → loki_reader_core-0.2.4}/tests/__init__.py +0 -0
  20. {loki_reader_core-0.2.3 → loki_reader_core-0.2.4}/tests/test_models.py +0 -0
  21. {loki_reader_core-0.2.3 → loki_reader_core-0.2.4}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: loki-reader-core
3
- Version: 0.2.3
3
+ Version: 0.2.4
4
4
  Summary: Python library for querying Grafana Loki logs via REST API
5
5
  Project-URL: Homepage, https://github.com/jmazzahacks/loki-reader-core
6
6
  Project-URL: Issues, https://github.com/jmazzahacks/loki-reader-core/issues
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "loki-reader-core"
3
- version = "0.2.3"
3
+ version = "0.2.4"
4
4
  authors = [
5
5
  { name="Jason Byteforge", email="jason@mzmail.me" },
6
6
  ]
@@ -6,7 +6,7 @@ from .client import LokiClient
6
6
  from .exceptions import LokiAuthError, LokiConnectionError, LokiError, LokiQueryError
7
7
  from .models import LogEntry, LogStream, MetricSample, MetricSeries, QueryResult, QueryStats
8
8
 
9
- __version__ = "0.2.3"
9
+ __version__ = "0.2.4"
10
10
 
11
11
  __all__ = [
12
12
  "LokiClient",
@@ -187,7 +187,7 @@ class LokiClient:
187
187
 
188
188
  self._session: Optional[requests.Session] = None
189
189
  self._app_label_cache: dict[str, str] = {}
190
- self._severity_label_cache: Optional[str] = None
190
+ self._severity_label_cache: dict[str, Optional[str]] = {}
191
191
 
192
192
  @property
193
193
  def session(self) -> requests.Session:
@@ -312,24 +312,32 @@ class LokiClient:
312
312
  f"({', '.join(APP_LABEL_NAMES)}). Use logql param for custom labels."
313
313
  )
314
314
 
315
- def _find_severity_label(self) -> Optional[str]:
316
- """Discover which label name is used for severity/level.
315
+ def _find_severity_label(self, app_label: str, app_value: str) -> Optional[str]:
316
+ """Discover which severity label name is used by a specific app.
317
317
 
318
- Checks a prioritized list of common severity label names and
319
- returns the first one that has values. Result is cached.
318
+ Queries the actual stream labels for the given app and checks
319
+ which severity label name appears. This avoids false matches
320
+ from other apps using a different severity label.
321
+
322
+ Args:
323
+ app_label: The label name for the application (e.g. "application").
324
+ app_value: The application name (e.g. "materia-server").
320
325
 
321
326
  Returns:
322
- The severity label name, or None if not found.
327
+ The severity label name for this app, or None if not found.
323
328
  """
324
- if self._severity_label_cache is not None:
325
- return self._severity_label_cache
329
+ if app_value in self._severity_label_cache:
330
+ return self._severity_label_cache[app_value]
331
+
332
+ series = self.get_series(match=['{' + f'{app_label}="{app_value}"' + '}'])
326
333
 
327
334
  for label_name in SEVERITY_LABEL_NAMES:
328
- values = self.get_label_values(label_name)
329
- if values:
330
- self._severity_label_cache = label_name
331
- return label_name
335
+ for s in series:
336
+ if label_name in s:
337
+ self._severity_label_cache[app_value] = label_name
338
+ return label_name
332
339
 
340
+ self._severity_label_cache[app_value] = None
333
341
  return None
334
342
 
335
343
  def query(
@@ -376,7 +384,7 @@ class LokiClient:
376
384
  app_label = self._find_app_label(app)
377
385
  selector = f'{app_label}="{app}"'
378
386
  if severity is not None:
379
- sev_label = self._find_severity_label()
387
+ sev_label = self._find_severity_label(app_label, app)
380
388
  if sev_label:
381
389
  regex = _build_severity_regex(severity)
382
390
  selector += f', {sev_label}=~"{regex}"'
@@ -75,7 +75,7 @@ class TestLokiClientInit:
75
75
  def test_init_label_caches_empty(self) -> None:
76
76
  client = LokiClient(base_url="http://localhost:3100")
77
77
  assert client._app_label_cache == {}
78
- assert client._severity_label_cache is None
78
+ assert client._severity_label_cache == {}
79
79
 
80
80
 
81
81
  class TestLokiClientSession:
@@ -474,19 +474,32 @@ class TestLabelDiscovery:
474
474
  with pytest.raises(ValueError, match="Could not find 'myapp'"):
475
475
  client._find_app_label("myapp")
476
476
 
477
- def test_find_severity_label(self) -> None:
477
+ def test_find_severity_label_from_series(self) -> None:
478
478
  client = LokiClient(base_url="http://localhost:3100")
479
- with patch.object(client, "get_label_values") as mock_glv:
480
- mock_glv.return_value = ["info", "error", "warn"]
481
- result = client._find_severity_label()
479
+ with patch.object(client, "get_series") as mock_gs:
480
+ mock_gs.return_value = [
481
+ {"application": "myapp", "severity": "info", "logger": "root"},
482
+ ]
483
+ result = client._find_severity_label("application", "myapp")
484
+ assert result == "severity"
485
+ mock_gs.assert_called_once_with(match=['{application="myapp"}'])
486
+
487
+ def test_find_severity_label_level(self) -> None:
488
+ client = LokiClient(base_url="http://localhost:3100")
489
+ with patch.object(client, "get_series") as mock_gs:
490
+ mock_gs.return_value = [
491
+ {"application": "myapp", "level": "info", "logger": "root"},
492
+ ]
493
+ result = client._find_severity_label("application", "myapp")
482
494
  assert result == "level"
483
- mock_glv.assert_called_once_with("level")
484
495
 
485
496
  def test_find_severity_label_none(self) -> None:
486
497
  client = LokiClient(base_url="http://localhost:3100")
487
- with patch.object(client, "get_label_values") as mock_glv:
488
- mock_glv.return_value = []
489
- result = client._find_severity_label()
498
+ with patch.object(client, "get_series") as mock_gs:
499
+ mock_gs.return_value = [
500
+ {"application": "myapp", "logger": "root"},
501
+ ]
502
+ result = client._find_severity_label("application", "myapp")
490
503
  assert result is None
491
504
 
492
505
  def test_label_discovery_cached(self) -> None:
@@ -502,13 +515,16 @@ class TestLabelDiscovery:
502
515
 
503
516
  def test_severity_label_cached(self) -> None:
504
517
  client = LokiClient(base_url="http://localhost:3100")
505
- with patch.object(client, "get_label_values") as mock_glv:
506
- mock_glv.return_value = ["info", "error"]
518
+ with patch.object(client, "get_series") as mock_gs:
519
+ mock_gs.return_value = [
520
+ {"application": "myapp", "severity": "info"},
521
+ ]
507
522
 
508
- client._find_severity_label()
509
- client._find_severity_label()
523
+ client._find_severity_label("application", "myapp")
524
+ client._find_severity_label("application", "myapp")
510
525
 
511
- mock_glv.assert_called_once_with("level")
526
+ # Only called once - second call uses cache
527
+ mock_gs.assert_called_once()
512
528
 
513
529
 
514
530
  class TestMergeStreams:
@@ -639,14 +655,14 @@ class TestLokiClientQueryRedesign:
639
655
  def test_query_app_with_severity(self) -> None:
640
656
  client = LokiClient(base_url="http://localhost:3100")
641
657
  with patch.object(client, "_find_app_label", return_value="application"), \
642
- patch.object(client, "_find_severity_label", return_value="level"), \
658
+ patch.object(client, "_find_severity_label", return_value="severity"), \
643
659
  patch.object(client, "query_range") as mock_qr:
644
660
  mock_qr.return_value = self._make_multi_stream_result()
645
661
 
646
662
  client.query(app="myapp", severity="error")
647
663
 
648
664
  args = mock_qr.call_args
649
- assert args.kwargs["logql"] == '{application="myapp", level=~"error|fatal|critical"}'
665
+ assert args.kwargs["logql"] == '{application="myapp", severity=~"error|fatal|critical"}'
650
666
 
651
667
  def test_query_app_since_minutes(self) -> None:
652
668
  client = LokiClient(base_url="http://localhost:3100")