Skip to content

Commit c62e2ea

Browse files
Merge pull request #171 from Aiven-Open/italo.garcia/mitigate-logentry-quota
2 parents 9803f9c + 95d94d7 commit c62e2ea

File tree

3 files changed

+71
-1
lines changed

3 files changed

+71
-1
lines changed

journalpump/senders/base.py

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
import re # type: ignore[no-redef]
1515

1616
KAFKA_COMPRESSED_MESSAGE_OVERHEAD = 30
17+
18+
# GCP logging also relies on this MAX message size
1719
MAX_KAFKA_MESSAGE_SIZE = 1024**2 # 1 MiB
1820

1921
MAX_ERROR_MESSAGES = 8

journalpump/senders/google_cloud_logging.py

+22-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,15 @@ class GoogleCloudLoggingSender(LogSender):
2323
0: "EMERGENCY",
2424
}
2525

26+
# A bit on the safe side, not exactly 256KB but this
27+
# is an approximation anyway
28+
# according to https://cloud.google.com/logging/quotas
29+
_LOG_ENTRY_QUOTA = 250 * 1024
30+
31+
# Somewhat arbitrary maximum message size choosen, this gives a 56K
32+
# headroom for the other fields in the LogEntry
33+
_MAX_MESSAGE_SIZE = 200 * 1024
34+
2635
def __init__(self, *, config, googleapiclient_request_builder=None, **kwargs):
2736
super().__init__(config=config, max_send_interval=config.get("max_send_interval", 0.3), **kwargs)
2837
credentials = None
@@ -62,6 +71,18 @@ def send_messages(self, *, messages, cursor):
6271
for message in messages:
6372
msg_str = message.decode("utf8")
6473
msg = json.loads(msg_str)
74+
75+
# This might not measure exactly 256K but should be a good enough approximation to handle this error.
76+
# We try truncating the message if it isn't possible then it is skip.
77+
if len(message) > self._LOG_ENTRY_QUOTA:
78+
DEFAULT_MESSAGE = "Log entry can't be logged because its size is greater than GCP logging quota of 256K"
79+
if "MESSAGE" in msg:
80+
msg["MESSAGE"] = f'{msg["MESSAGE"][:self._MAX_MESSAGE_SIZE]}[MESSAGE TRUNCATED]'
81+
messsage_size = len(json.dumps(msg, ensure_ascii=False).encode("utf-8"))
82+
if messsage_size > self._LOG_ENTRY_QUOTA:
83+
msg = {"MESSAGE": DEFAULT_MESSAGE}
84+
else:
85+
msg = {"MESSAGE": DEFAULT_MESSAGE}
6586
timestamp = msg.pop("timestamp", None)
6687
journald_priority = msg.pop("PRIORITY", None)
6788

@@ -75,7 +96,7 @@ def send_messages(self, *, messages, cursor):
7596
if timestamp is not None:
7697
entry["timestamp"] = timestamp[:26] + "Z" # assume timestamp to be UTC
7798
if journald_priority is not None:
78-
severity = GoogleCloudLoggingSender._SEVERITY_MAPPING.get(journald_priority, "DEFAULT")
99+
severity = self._SEVERITY_MAPPING.get(journald_priority, "DEFAULT")
79100
entry["severity"] = severity
80101
body["entries"].append(entry)
81102

test/test_google_cloud_logging.py

+47
Original file line numberDiff line numberDiff line change
@@ -144,3 +144,50 @@ def test_correct_timestamp(self):
144144
cursor=None,
145145
)
146146
assert sender._sent_count == 1 # pylint: disable=protected-access
147+
148+
def test_big_logentry_is_truncated(self):
149+
"""Check that message was not marked as sent if GoogleApi returns error"""
150+
message_content = "A" * 257_00
151+
request_builder = self._generate_request_builder(
152+
[{"jsonPayload": {"MESSAGE": message_content[: GoogleCloudLoggingSender._MAX_MESSAGE_SIZE]}}],
153+
)
154+
155+
sender = GoogleCloudLoggingSender(
156+
name="googlecloudlogging",
157+
reader=mock.Mock(),
158+
stats=mock.Mock(),
159+
field_filter=None,
160+
config=self.CONFIG,
161+
googleapiclient_request_builder=request_builder,
162+
)
163+
message = {"MESSAGE": message_content}
164+
sender.send_messages(messages=[json.dumps(message).encode()], cursor=None)
165+
assert sender._sent_count == 1
166+
167+
def test_big_logentry_sends_default(self):
168+
"""Check that message was not marked as sent if GoogleApi returns error"""
169+
request_builder = self._generate_request_builder(
170+
[
171+
{
172+
"jsonPayload": {
173+
"MESSAGE": "Log entry can't be logged because its size is greater than GCP logging quota of 256K"
174+
}
175+
}
176+
]
177+
)
178+
179+
sender = GoogleCloudLoggingSender(
180+
name="googlecloudlogging",
181+
reader=mock.Mock(),
182+
stats=mock.Mock(),
183+
field_filter=None,
184+
config=self.CONFIG,
185+
googleapiclient_request_builder=request_builder,
186+
)
187+
message = {"MESSAGE": "A" * 200_000, "OTHER_FIELD": "B" * 200_000}
188+
sender.send_messages(messages=[json.dumps(message).encode()], cursor=None)
189+
assert sender._sent_count == 1
190+
191+
message = {"OTHER_FIELD": "B" * 257_000}
192+
sender.send_messages(messages=[json.dumps(message).encode()], cursor=None)
193+
assert sender._sent_count == 2

0 commit comments

Comments
 (0)