@@ -298,6 +298,13 @@ static void flb_loki_kv_exit(struct flb_loki *ctx)
298298 mk_list_foreach_safe (head , tmp , & ctx -> structured_metadata_list ) {
299299 kv = mk_list_entry (head , struct flb_loki_kv , _head );
300300
301+ /* unlink and destroy */
302+ mk_list_del (& kv -> _head );
303+ flb_loki_kv_destroy (kv );
304+ }
305+ mk_list_foreach_safe (head , tmp , & ctx -> structured_metadata_map_keys_list ) {
306+ kv = mk_list_entry (head , struct flb_loki_kv , _head );
307+
301308 /* unlink and destroy */
302309 mk_list_del (& kv -> _head );
303310 flb_loki_kv_destroy (kv );
@@ -416,6 +423,87 @@ static void pack_kv(struct flb_loki *ctx,
416423 }
417424}
418425
426+ /* Similar to pack_kv above, except will only use msgpack_objects of type
427+ * MSGPACK_OBJECT_MAP, and will iterate over the keys adding each entry as a separate
428+ * item. Non-string map values are serialised to JSON, as Loki requires all values to be
429+ * strings.
430+ */
431+ static void pack_maps (struct flb_loki * ctx ,
432+ msgpack_packer * mp_pck ,
433+ char * tag , int tag_len ,
434+ msgpack_object * map ,
435+ struct flb_mp_map_header * mh ,
436+ struct mk_list * list )
437+ {
438+ struct mk_list * head ;
439+ struct flb_loki_kv * kv ;
440+
441+ msgpack_object * start_key ;
442+ msgpack_object * out_key ;
443+ msgpack_object * out_val ;
444+
445+ msgpack_object_map accessed_map ;
446+ uint32_t accessed_map_index ;
447+ msgpack_object_kv accessed_map_kv ;
448+
449+ char * accessed_map_val_json ;
450+
451+ mk_list_foreach (head , list ) {
452+ /* get the flb_loki_kv for this iteration of the loop */
453+ kv = mk_list_entry (head , struct flb_loki_kv , _head );
454+
455+ /* record accessor key/value pair */
456+ // TODO what if kv->ra_val != NULL?
457+ if (kv -> ra_key != NULL && kv -> ra_val == NULL ) {
458+
459+ /* try to get the value for the record accessor */
460+ if (flb_ra_get_kv_pair (kv -> ra_key , * map , & start_key , & out_key , & out_val )
461+ == MSGPACK_UNPACK_CONTINUE ) {
462+
463+ /* we require the value to be a map, or it doesn't make sense as this is
464+ * adding a map's key / values */
465+ if (out_val -> type != MSGPACK_OBJECT_MAP || out_val -> via .map .size <= 0 ) {
466+ flb_plg_debug (ctx -> ins , "No valid map data found for key %s" ,
467+ kv -> ra_key -> pattern );
468+ } else {
469+ accessed_map = out_val -> via .map ;
470+
471+ /* for each entry in the accessed map... */
472+ for (accessed_map_index = 0 ; accessed_map_index < accessed_map .size ;
473+ ++ accessed_map_index ) {
474+
475+ /* get the entry */
476+ accessed_map_kv = accessed_map .ptr [accessed_map_index ];
477+
478+ /* Pack the key and value */
479+ flb_mp_map_header_append (mh );
480+
481+ pack_label_key (mp_pck , (char * ) accessed_map_kv .key .via .str .ptr ,
482+ accessed_map_kv .key .via .str .size );
483+ /* Does this need optimising? For example, to handle bool as
484+ * non-JSON? */
485+ if (accessed_map_kv .val .type == MSGPACK_OBJECT_STR ) {
486+ msgpack_pack_str_with_body (mp_pck ,
487+ accessed_map_kv .val .via .str .ptr ,
488+ accessed_map_kv .val .via .str .size );
489+ }
490+ /* convert value to JSON, as Loki expects a string value */
491+ else {
492+ accessed_map_val_json = flb_msgpack_to_json_str (1024 ,
493+ & accessed_map_kv .val );
494+ if (accessed_map_val_json ) {
495+ msgpack_pack_str_with_body (mp_pck , accessed_map_val_json ,
496+ strlen (accessed_map_val_json ));
497+ flb_free (accessed_map_val_json );
498+ }
499+ }
500+ }
501+ }
502+ }
503+ }
504+ }
505+ }
506+
419507static flb_sds_t pack_structured_metadata (struct flb_loki * ctx ,
420508 msgpack_packer * mp_pck ,
421509 char * tag , int tag_len ,
@@ -424,7 +512,13 @@ static flb_sds_t pack_structured_metadata(struct flb_loki *ctx,
424512 struct flb_mp_map_header mh ;
425513 /* Initialize dynamic map header */
426514 flb_mp_map_header_init (& mh , mp_pck );
427- pack_kv (ctx , mp_pck , tag , tag_len , map , & mh , & ctx -> structured_metadata_list );
515+ if (ctx -> structured_metadata_map_keys ) {
516+ pack_maps (ctx , mp_pck , tag , tag_len , map , & mh , & ctx -> structured_metadata_map_keys_list );
517+ }
518+ // explicit structured_metadata entries override structured_metadata_map_keys entries
519+ if (ctx -> structured_metadata ) {
520+ pack_kv (ctx , mp_pck , tag , tag_len , map , & mh , & ctx -> structured_metadata_list );
521+ }
428522 flb_mp_map_header_end (& mh );
429523 return 0 ;
430524}
@@ -788,6 +882,7 @@ static int parse_labels(struct flb_loki *ctx)
788882
789883 flb_loki_kv_init (& ctx -> labels_list );
790884 flb_loki_kv_init (& ctx -> structured_metadata_list );
885+ flb_loki_kv_init (& ctx -> structured_metadata_map_keys_list );
791886
792887 if (ctx -> structured_metadata ) {
793888 ret = parse_kv (ctx , ctx -> structured_metadata , & ctx -> structured_metadata_list , & ra_used );
@@ -796,6 +891,26 @@ static int parse_labels(struct flb_loki *ctx)
796891 }
797892 }
798893
894+ /* Append structured metadata map keys set in the configuration */
895+ if (ctx -> structured_metadata_map_keys ) {
896+ mk_list_foreach (head , ctx -> structured_metadata_map_keys ) {
897+ entry = mk_list_entry (head , struct flb_slist_entry , _head );
898+ if (entry -> str [0 ] != '$' ) {
899+ flb_plg_error (ctx -> ins ,
900+ "invalid structured metadata map key, the name must start with '$'" );
901+ return -1 ;
902+ }
903+
904+ ret = flb_loki_kv_append (ctx , & ctx -> structured_metadata_map_keys_list , entry -> str , NULL );
905+ if (ret == -1 ) {
906+ return -1 ;
907+ }
908+ else if (ret > 0 ) {
909+ ra_used ++ ;
910+ }
911+ }
912+ }
913+
799914 if (ctx -> labels ) {
800915 ret = parse_kv (ctx , ctx -> labels , & ctx -> labels_list , & ra_used );
801916 if (ret == -1 ) {
@@ -971,6 +1086,7 @@ static struct flb_loki *loki_config_create(struct flb_output_instance *ins,
9711086 ctx -> ins = ins ;
9721087 flb_loki_kv_init (& ctx -> labels_list );
9731088 flb_loki_kv_init (& ctx -> structured_metadata_list );
1089+ flb_loki_kv_init (& ctx -> structured_metadata_map_keys_list );
9741090
9751091 /* Register context with plugin instance */
9761092 flb_output_set_context (ins , ctx );
@@ -1539,12 +1655,13 @@ static flb_sds_t loki_compose_payload(struct flb_loki *ctx,
15391655 while ((ret = flb_log_event_decoder_next (
15401656 & log_decoder ,
15411657 & log_event )) == FLB_EVENT_DECODER_SUCCESS ) {
1542- msgpack_pack_array (& mp_pck , ctx -> structured_metadata ? 3 : 2 );
1658+ msgpack_pack_array (& mp_pck , ctx -> structured_metadata ||
1659+ ctx -> structured_metadata_map_keys ? 3 : 2 );
15431660
15441661 /* Append the timestamp */
15451662 pack_timestamp (& mp_pck , & log_event .timestamp );
15461663 pack_record (ctx , & mp_pck , log_event .body , dynamic_tenant_id );
1547- if (ctx -> structured_metadata ) {
1664+ if (ctx -> structured_metadata || ctx -> structured_metadata_map_keys ) {
15481665 pack_structured_metadata (ctx , & mp_pck , tag , tag_len , NULL );
15491666 }
15501667 }
@@ -1575,12 +1692,13 @@ static flb_sds_t loki_compose_payload(struct flb_loki *ctx,
15751692 msgpack_pack_str_body (& mp_pck , "values" , 6 );
15761693 msgpack_pack_array (& mp_pck , 1 );
15771694
1578- msgpack_pack_array (& mp_pck , ctx -> structured_metadata ? 3 : 2 );
1695+ msgpack_pack_array (& mp_pck , ctx -> structured_metadata ||
1696+ ctx -> structured_metadata_map_keys ? 3 : 2 );
15791697
15801698 /* Append the timestamp */
15811699 pack_timestamp (& mp_pck , & log_event .timestamp );
15821700 pack_record (ctx , & mp_pck , log_event .body , dynamic_tenant_id );
1583- if (ctx -> structured_metadata ) {
1701+ if (ctx -> structured_metadata || ctx -> structured_metadata_map_keys ) {
15841702 pack_structured_metadata (ctx , & mp_pck , tag , tag_len , log_event .body );
15851703 }
15861704 }
@@ -1905,6 +2023,13 @@ static struct flb_config_map config_map[] = {
19052023 0 , FLB_TRUE , offsetof(struct flb_loki , structured_metadata ),
19062024 "optional structured metadata fields for API requests."
19072025 },
2026+
2027+ {
2028+ FLB_CONFIG_MAP_CLIST , "structured_metadata_map_keys" , NULL ,
2029+ 0 , FLB_TRUE , offsetof(struct flb_loki , structured_metadata_map_keys ),
2030+ "optional structured metadata fields, as derived dynamically from configured maps "
2031+ "keys, for API requests."
2032+ },
19082033
19092034 {
19102035 FLB_CONFIG_MAP_BOOL , "auto_kubernetes_labels" , "false" ,
0 commit comments