Skip to content

Commit 553898e

Browse files
committed
out_cloudwatch_logs: increase MAX_EVENT_LEN to 1MB with tests
Increase MAX_EVENT_LEN from 262,118 bytes (256 KiB) to 1,000,000 bytes (1 MB) to better align with AWS CloudWatch's documented maximum event size of 1,048,576 bytes (1 MiB). The 1 MB limit provides a ~4.6% safety margin to account for JSON encoding overhead. Testing confirmed messages up to 1,048,546 bytes (encoding to 1,048,586 bytes) succeed, though we use a conservative limit for production safety. Add runtime tests to validate the new limit: - event_size_at_limit: Validates events at exactly MAX_EVENT_LEN (1MB) are accepted - event_size_over_limit: Validates events exceeding MAX_EVENT_LEN are truncated - event_truncation_with_backslash: Validates backslash handling at truncation boundary Signed-off-by: Shelby Hagman <[email protected]>
1 parent 4f8c50b commit 553898e

File tree

2 files changed

+164
-5
lines changed

2 files changed

+164
-5
lines changed

plugins/out_cloudwatch_logs/cloudwatch_api.h

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@
2121
#define FLB_OUT_CLOUDWATCH_API
2222

2323
/*
24-
* The CloudWatch API documents that the maximum payload is 1,048,576 bytes
25-
* For reasons that are under investigation, using that number in this plugin
26-
* leads to API errors. No issues have been seen setting it to 1,000,000 bytes.
24+
* The CloudWatch API documents that the maximum payload is 1,048,576 bytes.
25+
* This is the total size limit for the entire PutLogEvents request payload buffer.
26+
* Individual events are capped at MAX_EVENT_LEN (1,000,000 bytes) as a conservative
27+
* safety margin to account for JSON encoding overhead and per-event metadata.
2728
*/
2829
#define PUT_LOG_EVENTS_PAYLOAD_SIZE 1048576
2930
#define MAX_EVENTS_PER_PUT 10000
@@ -43,8 +44,17 @@
4344
/* Maximum number of character limits including both the Attributes key and its value */
4445
#define ATTRIBUTES_MAX_LEN 300
4546

46-
/* 256KiB minus 26 bytes for the event */
47-
#define MAX_EVENT_LEN 262118
47+
/*
48+
* https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_PutLogEvents.html
49+
* AWS CloudWatch's documented maximum event size is 1,048,576 bytes (1 MiB),
50+
* including JSON encoding overhead (structure, escaping, etc.).
51+
*
52+
* Setting MAX_EVENT_LEN to 1,000,000 bytes (1 MB) provides a ~4.6% safety margin
53+
* to account for JSON encoding overhead and ensure reliable operation.
54+
* Testing confirmed messages up to 1,048,546 bytes (encoding to 1,048,586 bytes)
55+
* succeed, though we use a conservative limit for production safety.
56+
*/
57+
#define MAX_EVENT_LEN 1000000
4858

4959
/* Prefix used for entity fields only */
5060
#define AWS_ENTITY_PREFIX "aws_entity"

tests/runtime/out_cloudwatch.c

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,17 @@
55
/* Test data */
66
#include "data/td/json_td.h" /* JSON_TD */
77

8+
/* CloudWatch API constants */
9+
#include "../../plugins/out_cloudwatch_logs/cloudwatch_api.h"
10+
811
#define ERROR_ALREADY_EXISTS "{\"__type\":\"ResourceAlreadyExistsException\"}"
912
/* not a real error code, but tests that the code can respond to any error */
1013
#define ERROR_UNKNOWN "{\"__type\":\"UNKNOWN\"}"
1114

15+
/* JSON structure constants for test message generation */
16+
static const char *TEST_JSON_PREFIX = "{\"message\":\"";
17+
static const char *TEST_JSON_SUFFIX = "\"}";
18+
1219
/* It writes a big JSON message (copied from TD test) */
1320
void flb_test_cloudwatch_success(void)
1421
{
@@ -393,6 +400,145 @@ void flb_test_cloudwatch_error_put_retention_policy(void)
393400
flb_destroy(ctx);
394401
}
395402

403+
/* Helper function to create a large JSON message of specified size */
404+
static char* create_large_json_message(size_t target_size)
405+
{
406+
size_t prefix_len = strlen(TEST_JSON_PREFIX);
407+
size_t suffix_len = strlen(TEST_JSON_SUFFIX);
408+
size_t overhead = prefix_len + suffix_len;
409+
size_t data_size;
410+
char *json;
411+
412+
/* Reject target_size too small for valid JSON structure */
413+
if (target_size < overhead + 1) {
414+
return NULL;
415+
}
416+
417+
json = flb_malloc(target_size + 1);
418+
if (!json) {
419+
return NULL;
420+
}
421+
422+
/* Build JSON: prefix + data + suffix */
423+
memcpy(json, TEST_JSON_PREFIX, prefix_len);
424+
data_size = target_size - overhead;
425+
426+
/* Fill with 'A' characters */
427+
memset(json + prefix_len, 'A', data_size);
428+
429+
/* Close JSON object */
430+
memcpy(json + prefix_len + data_size, TEST_JSON_SUFFIX, suffix_len);
431+
json[target_size] = '\0';
432+
433+
/* Caller must free */
434+
return json;
435+
}
436+
437+
/* Helper to setup and run a CloudWatch test with custom JSON data */
438+
static void run_cloudwatch_test_with_data(char *data, size_t data_len)
439+
{
440+
int ret;
441+
flb_ctx_t *ctx;
442+
int in_ffd;
443+
int out_ffd;
444+
445+
setenv("FLB_CLOUDWATCH_PLUGIN_UNDER_TEST", "true", 1);
446+
447+
ctx = flb_create();
448+
TEST_CHECK(ctx != NULL);
449+
450+
in_ffd = flb_input(ctx, (char *) "lib", NULL);
451+
TEST_CHECK(in_ffd >= 0);
452+
flb_input_set(ctx, in_ffd, "tag", "test", NULL);
453+
454+
out_ffd = flb_output(ctx, (char *) "cloudwatch_logs", NULL);
455+
TEST_CHECK(out_ffd >= 0);
456+
flb_output_set(ctx, out_ffd, "match", "test", NULL);
457+
flb_output_set(ctx, out_ffd, "region", "us-west-2", NULL);
458+
flb_output_set(ctx, out_ffd, "log_group_name", "fluent", NULL);
459+
flb_output_set(ctx, out_ffd, "log_stream_prefix", "from-fluent-", NULL);
460+
flb_output_set(ctx, out_ffd, "auto_create_group", "On", NULL);
461+
flb_output_set(ctx, out_ffd, "net.keepalive", "Off", NULL);
462+
flb_output_set(ctx, out_ffd, "Retry_Limit", "1", NULL);
463+
464+
ret = flb_start(ctx);
465+
TEST_CHECK(ret == 0);
466+
467+
if (data) {
468+
flb_lib_push(ctx, in_ffd, data, data_len);
469+
}
470+
471+
sleep(2);
472+
flb_stop(ctx);
473+
flb_destroy(ctx);
474+
}
475+
476+
/* Test event size at maximum allowed limit (should succeed without truncation) */
477+
void flb_test_cloudwatch_event_size_at_limit(void)
478+
{
479+
char *large_json;
480+
481+
/* Create message at MAX_EVENT_LEN */
482+
large_json = create_large_json_message(MAX_EVENT_LEN);
483+
TEST_CHECK(large_json != NULL);
484+
485+
if (large_json) {
486+
run_cloudwatch_test_with_data(large_json, strlen(large_json));
487+
flb_free(large_json);
488+
}
489+
}
490+
491+
/* Test event size exceeding limit (should be truncated to MAX_EVENT_LEN) */
492+
void flb_test_cloudwatch_event_size_over_limit(void)
493+
{
494+
char *large_json;
495+
496+
/* Create message exceeding MAX_EVENT_LEN by 1 byte to test truncation */
497+
large_json = create_large_json_message(MAX_EVENT_LEN + 1);
498+
TEST_CHECK(large_json != NULL);
499+
500+
if (large_json) {
501+
run_cloudwatch_test_with_data(large_json, strlen(large_json));
502+
flb_free(large_json);
503+
}
504+
}
505+
506+
/* Test event with trailing backslash at truncation boundary */
507+
void flb_test_cloudwatch_event_truncation_with_backslash(void)
508+
{
509+
char *large_json;
510+
size_t prefix_len = strlen(TEST_JSON_PREFIX);
511+
size_t suffix_len = strlen(TEST_JSON_SUFFIX);
512+
size_t total_len;
513+
size_t data_len;
514+
size_t i;
515+
516+
/* Create base message exceeding MAX_EVENT_LEN */
517+
large_json = create_large_json_message(MAX_EVENT_LEN + 100);
518+
TEST_CHECK(large_json != NULL);
519+
520+
if (large_json) {
521+
total_len = strlen(large_json);
522+
data_len = total_len - prefix_len - suffix_len;
523+
524+
/* Replace pairs of characters with valid escape sequence "\\" */
525+
for (i = 98; i < data_len - 1; i += 100) {
526+
large_json[prefix_len + i] = '\\';
527+
large_json[prefix_len + i + 1] = '\\';
528+
}
529+
530+
size_t boundary = MAX_EVENT_LEN - 1; /* index in full JSON string */
531+
/* Ensure a backslash is at the exact truncation boundary */
532+
if (boundary + 1 < total_len - suffix_len) {
533+
large_json[boundary] = '\\';
534+
large_json[boundary + 1] = '\\';
535+
}
536+
537+
run_cloudwatch_test_with_data(large_json, strlen(large_json));
538+
flb_free(large_json);
539+
}
540+
}
541+
396542
/* Test list */
397543
TEST_LIST = {
398544
{"success", flb_test_cloudwatch_success },
@@ -405,5 +551,8 @@ TEST_LIST = {
405551
{"put_retention_policy_success", flb_test_cloudwatch_put_retention_policy_success },
406552
{"already_exists_create_group_put_retention_policy", flb_test_cloudwatch_already_exists_create_group_put_retention_policy },
407553
{"error_put_retention_policy", flb_test_cloudwatch_error_put_retention_policy },
554+
{"event_size_at_limit", flb_test_cloudwatch_event_size_at_limit },
555+
{"event_size_over_limit", flb_test_cloudwatch_event_size_over_limit },
556+
{"event_truncation_with_backslash", flb_test_cloudwatch_event_truncation_with_backslash },
408557
{NULL, NULL}
409558
};

0 commit comments

Comments
 (0)