Skip to content

Commit e2a9a30

Browse files
authored
Lumigo tracer enhanced programmatic logger (#189)
* added info, warn, and error to tracer API
1 parent 26eaeb8 commit e2a9a30

File tree

4 files changed

+163
-21
lines changed

4 files changed

+163
-21
lines changed

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ jobs:
115115
<<: *defaults
116116
steps:
117117
- checkout_code
118-
- checkout_credentials
118+
- checkout_utils
119119
# run tests!
120120
- run: echo "export AWS_DEFAULT_REGION=us-west-2" >> $BASH_ENV
121121
- run: mkdir -p ~/.aws

src/lumigo_tracer/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
from .tracer import lumigo_tracer, LumigoChalice # noqa
2-
from .user_utils import report_error, add_execution_tag # noqa
2+
from .user_utils import report_error, add_execution_tag, info, warn, error # noqa
33
from .auto_instrument_handler import _handler # noqa

src/lumigo_tracer/user_utils.py

Lines changed: 98 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,82 @@
1+
import json
2+
import logging
3+
from typing import Dict, Optional
4+
15
from lumigo_tracer.spans_container import SpansContainer
26
from lumigo_tracer.lumigo_utils import Configuration, warn_client
37

48
LUMIGO_REPORT_ERROR_STRING = "[LUMIGO_LOG]"
59
MAX_TAGS = 50
10+
MAX_ELEMENTS_IN_EXTRA = 10
611
MAX_TAG_KEY_LEN = MAX_TAG_VALUE_LEN = 50
712
ADD_TAG_ERROR_MSG_PREFIX = "Skipping add_execution_tag: Unable to add tag"
813

914

15+
def info(msg: str, alert_type: str = "ProgrammaticInfo", extra: Dict[str, str] = None):
16+
"""
17+
Use this function to create a log entry in your lumigo platform.
18+
You can use it to dynamically generate alerts programmatically with searchable fields.
19+
Then use the lumigo explore to search and filters logs in free text.
20+
21+
:param msg: a free text to log
22+
:param alert_type: Should be considered as a grouping parameter. This indicates the type of this message. Default: ProgrammaticInfo
23+
:param extra: a key-value dict. Limited to 10 keys and 50 characters per value.
24+
"""
25+
log(logging.INFO, msg, alert_type, extra)
26+
27+
28+
def warn(msg: str, alert_type: str = "ProgrammaticWarn", extra: Dict[str, str] = None):
29+
"""
30+
Use this function to create a log entry in your lumigo platform.
31+
You can use it to dynamically generate alerts programmatically with searchable fields.
32+
Then use the lumigo explore to search and filters logs in free text.
33+
34+
:param msg: a free text to log
35+
:param alert_type: Should be considered as a grouping parameter. This indicates the type of this message. Default: ProgrammaticWarn
36+
:param extra: a key-value dict. Limited to 10 keys and 50 characters per value.
37+
"""
38+
log(logging.WARN, msg, alert_type, extra)
39+
40+
41+
def error(
42+
msg: str,
43+
alert_type: Optional[str] = None,
44+
extra: Optional[Dict[str, str]] = None,
45+
err: Optional[Exception] = None,
46+
):
47+
"""
48+
Use this function to create a log entry in your lumigo platform.
49+
You can use it to dynamically generate alerts programmatically with searchable fields.
50+
Then use the lumigo explore to search and filters logs in free text.
51+
52+
:param msg: a free text to log
53+
:param alert_type: Should be considered as a grouping parameter. This indicates the type of this message. Default: take the given exception type or ProgrammaticError if its None
54+
:param extra: a key-value dict. Limited to 10 keys and 50 characters per value. By default we're taking the excpetion raw message
55+
:param err: the actual error object.
56+
"""
57+
58+
extra = extra or {}
59+
if err:
60+
extra["raw_exception"] = str(err)
61+
alert_type = alert_type or err.__class__.__name__
62+
alert_type = alert_type or "ProgrammaticError"
63+
log(logging.ERROR, msg, alert_type, extra)
64+
65+
66+
def log(level: int, msg: str, error_type: str, extra: Optional[Dict[str, str]]):
67+
filtered_extra = list(
68+
filter(
69+
lambda element: validate_tag(element[0], element[1], 0, True),
70+
(extra or {}).items(),
71+
)
72+
)
73+
extra = {key: str(value) for key, value in filtered_extra[:MAX_ELEMENTS_IN_EXTRA]}
74+
actual = {"message": msg, "type": error_type, "level": level}
75+
if extra:
76+
actual["extra"] = extra
77+
print(LUMIGO_REPORT_ERROR_STRING, json.dumps(actual))
78+
79+
1080
def report_error(msg: str):
1181
message_with_initials = f"{LUMIGO_REPORT_ERROR_STRING} {msg}"
1282
if Configuration.enhanced_print:
@@ -16,6 +86,30 @@ def report_error(msg: str):
1686
print(message_with_request_id)
1787

1888

89+
def validate_tag(key, value, tags_len, should_log_errors):
90+
value = str(value)
91+
if not key or len(key) >= MAX_TAG_KEY_LEN:
92+
if should_log_errors:
93+
warn_client(
94+
f"{ADD_TAG_ERROR_MSG_PREFIX}: key length should be between 1 and {MAX_TAG_KEY_LEN}: {key} - {value}"
95+
)
96+
return False
97+
if not value or len(value) >= MAX_TAG_VALUE_LEN:
98+
if should_log_errors:
99+
warn_client(
100+
f"{ADD_TAG_ERROR_MSG_PREFIX}: value length should be between 1 and {MAX_TAG_VALUE_LEN}: {key} - {value}"
101+
)
102+
return False
103+
if tags_len >= MAX_TAGS:
104+
if should_log_errors:
105+
warn_client(
106+
f"{ADD_TAG_ERROR_MSG_PREFIX}: maximum number of tags is {MAX_TAGS}: {key} - {value}"
107+
)
108+
return False
109+
110+
return True
111+
112+
19113
def add_execution_tag(key: str, value: str, should_log_errors: bool = True) -> bool:
20114
"""
21115
Use this function to add an execution_tag to your function with a dynamic value.
@@ -27,28 +121,13 @@ def add_execution_tag(key: str, value: str, should_log_errors: bool = True) -> b
27121
:param should_log_errors: Should a log message be printed in case the tag can't be added.
28122
"""
29123
try:
30-
tags_len = SpansContainer.get_span().get_tags_len()
31124
key = str(key)
32125
value = str(value)
33-
if not key or len(key) >= MAX_TAG_KEY_LEN:
34-
if should_log_errors:
35-
warn_client(
36-
f"{ADD_TAG_ERROR_MSG_PREFIX}: key length should be between 1 and {MAX_TAG_KEY_LEN}: {key} - {value}"
37-
)
38-
return False
39-
if not value or len(value) >= MAX_TAG_VALUE_LEN:
40-
if should_log_errors:
41-
warn_client(
42-
f"{ADD_TAG_ERROR_MSG_PREFIX}: value length should be between 1 and {MAX_TAG_VALUE_LEN}: {key} - {value}"
43-
)
44-
return False
45-
if tags_len >= MAX_TAGS:
46-
if should_log_errors:
47-
warn_client(
48-
f"{ADD_TAG_ERROR_MSG_PREFIX}: maximum number of tags is {MAX_TAGS}: {key} - {value}"
49-
)
126+
tags_len = SpansContainer.get_span().get_tags_len()
127+
if validate_tag(key, value, tags_len, should_log_errors):
128+
SpansContainer.get_span().add_tag(key, value)
129+
else:
50130
return False
51-
SpansContainer.get_span().add_tag(key, value)
52131
except Exception:
53132
if should_log_errors:
54133
warn_client(ADD_TAG_ERROR_MSG_PREFIX)

src/test/unit/test_user_utils.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
from lumigo_tracer.spans_container import SpansContainer
22
from lumigo_tracer.user_utils import (
33
report_error,
4+
warn,
5+
info,
6+
error,
47
LUMIGO_REPORT_ERROR_STRING,
58
add_execution_tag,
69
MAX_TAG_KEY_LEN,
710
MAX_TAG_VALUE_LEN,
811
MAX_TAGS,
12+
MAX_ELEMENTS_IN_EXTRA,
913
)
1014
from lumigo_tracer.lumigo_utils import Configuration, EXECUTION_TAGS_KEY
1115

@@ -18,6 +22,65 @@ def test_report_error_with_enhance_print(capsys):
1822
assert captured.out == f"{LUMIGO_REPORT_ERROR_STRING} {msg}\n"
1923

2024

25+
def test_err_without_alert_type_with_exception(capsys):
26+
msg = '{"message": "This is error message", "type": "RuntimeError", "level": 40, "extra": {"a": "3", "b": "True", "c": "aaa", "d": "{}", "aa": "a", "a0": "0", "a1": "1", "a2": "2", "a3": "3", "a4": "4"}}'
27+
error(
28+
err=RuntimeError("Failed to open database"),
29+
msg="This is error message",
30+
extra={
31+
"a": 3,
32+
"b": True,
33+
"c": "aaa",
34+
"d": {},
35+
"aa": "a",
36+
"A" * 100: "A" * 100,
37+
**{f"a{i}": i for i in range(MAX_ELEMENTS_IN_EXTRA)},
38+
},
39+
)
40+
captured = capsys.readouterr().out.split("\n")
41+
assert captured[1] == f"{LUMIGO_REPORT_ERROR_STRING} {msg}"
42+
43+
44+
def test_err_with_type_and_exception(capsys):
45+
msg = (
46+
'{"message": "This is error message", "type": "DBError",'
47+
' "level": 40, "extra": {"raw_exception": "Failed to open database"}}'
48+
)
49+
error(
50+
err=RuntimeError("Failed to open database"),
51+
msg="This is error message",
52+
alert_type="DBError",
53+
)
54+
captured = capsys.readouterr().out.split("\n")
55+
assert captured[0] == f"{LUMIGO_REPORT_ERROR_STRING} {msg}"
56+
57+
58+
def test_err_with_no_type_and_no_exception(capsys):
59+
msg = '{"message": "This is error message", "type": "ProgrammaticError", "level": 40}'
60+
error(
61+
msg="This is error message",
62+
)
63+
captured = capsys.readouterr().out.split("\n")
64+
assert captured[0] == f"{LUMIGO_REPORT_ERROR_STRING} {msg}"
65+
66+
67+
def test_basic_info_warn_error(capsys):
68+
info("This is error message")
69+
warn("This is error message")
70+
error("This is error message")
71+
info_msg = (
72+
'[LUMIGO_LOG] {"message": "This is error message", "type": "ProgrammaticInfo", "level": 20}'
73+
)
74+
warn_msg = (
75+
'[LUMIGO_LOG] {"message": "This is error message", "type": "ProgrammaticWarn", "level": 30}'
76+
)
77+
error_msg = '[LUMIGO_LOG] {"message": "This is error message", "type": "ProgrammaticError", "level": 40}'
78+
captured = capsys.readouterr().out.split("\n")
79+
assert captured[0] == info_msg
80+
assert captured[1] == warn_msg
81+
assert captured[2] == error_msg
82+
83+
2184
def test_report_error_without_enhance_print(capsys):
2285
Configuration.enhanced_print = False
2386
SpansContainer.get_span().function_span["id"] = "123"

0 commit comments

Comments
 (0)