Skip to content

Commit f844d36

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 <shelbyzh@amazon.com>
1 parent 4f8c50b commit f844d36

File tree

2 files changed

+155
-2
lines changed

2 files changed

+155
-2
lines changed

plugins/out_cloudwatch_logs/cloudwatch_api.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,17 @@
4343
/* Maximum number of character limits including both the Attributes key and its value */
4444
#define ATTRIBUTES_MAX_LEN 300
4545

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

4958
/* Prefix used for entity fields only */
5059
#define AWS_ENTITY_PREFIX "aws_entity"

tests/runtime/out_cloudwatch.c

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

0 commit comments

Comments
 (0)