Skip to content
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
33 changes: 22 additions & 11 deletions logging_loki/emitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ class LokiEmitter:
"""Base Loki emitter class."""

success_response_code = const.success_response_code
level_tag = const.level_tag
logger_tag = const.logger_tag
label_allowed_chars = const.label_allowed_chars
label_replace_with = const.label_replace_with
session_class = requests.Session
Expand All @@ -32,7 +30,9 @@ def __init__(self,
headers: Optional[dict] = None,
auth: BasicAuth = None,
as_json: bool = False,
props_to_labels: Optional[list[str]] = None
props_to_labels: Optional[list[str]] = None,
level_tag: Optional[str] = const.level_tag,
logger_tag: Optional[str] = const.logger_tag
):
"""
Create new Loki emitter.
Expand All @@ -53,8 +53,12 @@ def __init__(self,
self.auth = auth
#: Optional bool, send record as json?
self.as_json = as_json
#: Optional list, send record as json?
#: Optional list, convert properties to loki labels
self.props_to_labels = props_to_labels or []
#: Label name indicating logging level.
self.level_tag: str = level_tag
#: Label name indicating logger name.
self.logger_tag: str = logger_tag

self._session: Optional[requests.Session] = None
self._lock = threading.Lock()
Expand Down Expand Up @@ -102,14 +106,21 @@ def format_label(self, label: str) -> str:
label = label.replace(char_from, char_to)
return "".join(char for char in label if char in self.label_allowed_chars)

def build_tags(self, record: logging.LogRecord) -> Dict[str, Any]:
def build_tags(self, record: logging.LogRecord, line: str) -> Dict[str, Any]:
"""Return tags that must be send to Loki with a log record."""
tags = dict(self.tags) if isinstance(self.tags, ConvertingDict) else self.tags
tags = copy.deepcopy(tags)
tags[self.level_tag] = record.levelname.lower()
tags[self.logger_tag] = record.name

extra_tags = {k: getattr(record, k) for k in self.props_to_labels if getattr(record, k, None)}
if self.level_tag:
tags[self.level_tag] = record.levelname.lower()
if self.logger_tag:
tags[self.logger_tag] = record.name

extra_tags = {}
if self.props_to_labels:
jsonline = json.loads(line)
for k in self.props_to_labels:
if prop_value := getattr(record, k, None) or jsonline.get(k, None):
extra_tags.update({k: prop_value})
if isinstance(passed_tags := getattr(record, "tags", {}), dict):
extra_tags = extra_tags | passed_tags

Expand All @@ -121,9 +132,9 @@ def build_tags(self, record: logging.LogRecord) -> Dict[str, Any]:

return tags

def build_payload(self, record: logging.LogRecord, line) -> dict:
def build_payload(self, record: logging.LogRecord, line: str) -> dict:
"""Build JSON payload with a log entry."""
labels = self.build_tags(record)
labels = self.build_tags(record, line)
ns = 1e9
ts = str(int(time.time() * ns))

Expand Down
7 changes: 6 additions & 1 deletion logging_loki/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from queue import Queue
import time
from typing import Optional, Union
from logging_loki import const

from logging_loki.emitter import BasicAuth, LokiEmitter

Expand Down Expand Up @@ -51,6 +52,8 @@ def __init__(
auth: Optional[BasicAuth] = None,
as_json: Optional[bool] = False,
props_to_labels: Optional[list[str]] = None,
level_tag: Optional[str] = const.level_tag,
logger_tag: Optional[str] = const.logger_tag
):
"""
Create new Loki logging handler.
Expand All @@ -62,10 +65,12 @@ def __init__(
headers: Optional record with headers that are send with each POST to loki.
as_json: Flag to support sending entire JSON record instead of only the message.
props_to_labels: List of properties that should be converted to loki labels.
level_tag: Label name indicating logging level.
logger_tag: Label name indicating logger name.

"""
super().__init__()
self.emitter = LokiEmitter(url, tags, headers, auth, as_json, props_to_labels)
self.emitter = LokiEmitter(url, tags, headers, auth, as_json, props_to_labels, level_tag, logger_tag)

def handleError(self, record): # noqa: N802
"""Close emitter and let default handler take actions on error."""
Expand Down
2 changes: 1 addition & 1 deletion tests/test_emitter_v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ def test_can_build_tags_from_converting_dict(emitter_v1):

logger = logging.getLogger(logger_name)
emitter: LokiEmitter = logger.handlers[0].handler.emitter
emitter.build_tags(create_record())
emitter.build_tags(create_record(), '{}')

def test_batch_records_sent_to_emitter_url(emitter_v1):
emitter, session = emitter_v1
Expand Down