From 2ef6c3a49a9cd24ecb843ae9747e0f3428dd2bfb Mon Sep 17 00:00:00 2001 From: Amitsingh Tanwar Date: Fri, 7 Aug 2026 10:27:56 +0530 Subject: [PATCH 01/12] feat(logs): config-driven log transformations and static values for golden log lines Add Euler-compatible log schema support: - Config-driven field renaming for golden log lines via [log.transformations.incoming] and [log.transformations.outgoing] (gated behind log-transformations feature flag) - Runtime static key-value pairs via [log.static_values], overridable per-request through x-config-override header - transformation_mode (copy/replace) for event field transformations - Deadlock fix: filter reserved keys before entering span write lock to prevent re-entrant deadlock from log_utils record_value calling tracing::warn! Co-Authored-By: Claude Opus 4.6 --- Cargo.lock | 1016 +++++++++-------- crates/common/common_utils/Cargo.toml | 2 + crates/common/common_utils/src/events.rs | 209 +++- crates/common/external-services/Cargo.toml | 5 + .../common/external-services/src/service.rs | 45 + crates/common/tracing-kafka/Cargo.toml | 2 +- crates/common/ucs_env/Cargo.toml | 7 +- crates/common/ucs_env/src/configs.rs | 26 +- crates/common/ucs_env/src/logger/config.rs | 25 + crates/common/ucs_env/src/logger/setup.rs | 2 +- crates/grpc-server/grpc-server/Cargo.toml | 8 +- .../grpc-server/src/server/disputes.rs | 6 + .../grpc-server/src/server/events.rs | 18 + .../grpc-server/src/server/payments.rs | 24 + crates/grpc-server/grpc-server/src/utils.rs | 112 +- 15 files changed, 1027 insertions(+), 480 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 04db86a3a6..56fc402439 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -168,7 +168,7 @@ dependencies = [ "serde_urlencoded", "smallvec", "socket2 0.6.3", - "time 0.3.47", + "time 0.3.55", "tracing", "url 2.5.8", ] @@ -315,7 +315,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -326,14 +326,14 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "arc-swap" @@ -392,7 +392,7 @@ dependencies = [ "memchr", "proc-macro2", "quote", - "rustc-hash 2.1.1", + "rustc-hash 2.1.3", "serde", "serde_derive", "syn 2.0.117", @@ -662,6 +662,21 @@ dependencies = [ "serde", ] +[[package]] +name = "bisync" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5020822f6d6f23196ccaf55e228db36f9de1cf788052b37992e17cbc96ec41a7" +dependencies = [ + "bisync_macros", +] + +[[package]] +name = "bisync_macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d21f40d350a700f6aa107e45fb26448cf489d34794b2ba4522181dc9f1173af6" + [[package]] name = "bit-set" version = "0.5.3" @@ -752,6 +767,31 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bon" +version = "3.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a602c73c7b0148ec6d12af6fd5cc7a46e2eacc8878271a999abac56eed12f561" +dependencies = [ + "bon-macros", + "rustversion", +] + +[[package]] +name = "bon-macros" +version = "3.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dee98b0db6a962de883bf5d20362dee4d7ca0d12fe39a7c6c73c844e1cd7c1f" +dependencies = [ + "darling 0.21.3", + "ident_case", + "prettyplease", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.117", +] + [[package]] name = "borsh" version = "1.6.1" @@ -821,9 +861,9 @@ dependencies = [ [[package]] name = "build_info" version = "0.1.0" -source = "git+https://github.com/juspay/framework-libs-rs?rev=243562041252fe5897ce888d20b715ffdc3767ce#243562041252fe5897ce888d20b715ffdc3767ce" +source = "git+https://github.com/juspay/framework-libs-rs?rev=5d0b5824b2a82dafae492f9930c95060507d91b4#5d0b5824b2a82dafae492f9930c95060507d91b4" dependencies = [ - "cargo_metadata 0.19.2", + "cargo_metadata 0.23.1", "vergen-gix", ] @@ -912,6 +952,16 @@ dependencies = [ "serde", ] +[[package]] +name = "cargo-platform" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0061da739915fae12ea00e16397555ed4371a6bb285431aab930f61b0aa4ba" +dependencies = [ + "serde", + "serde_core", +] + [[package]] name = "cargo_metadata" version = "0.18.1" @@ -919,7 +969,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" dependencies = [ "camino", - "cargo-platform", + "cargo-platform 0.1.9", "semver", "serde", "serde_json", @@ -933,7 +983,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" dependencies = [ "camino", - "cargo-platform", + "cargo-platform 0.1.9", + "semver", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "cargo_metadata" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9" +dependencies = [ + "camino", + "cargo-platform 0.3.3", "semver", "serde", "serde_json", @@ -1173,7 +1237,7 @@ dependencies = [ "serde_urlencoded", "strum 0.26.3", "thiserror 1.0.69", - "time 0.3.47", + "time 0.3.55", "url 2.5.8", "urlencoding", "utoipa", @@ -1280,7 +1344,7 @@ dependencies = [ "sha2", "strum 0.26.3", "thiserror 1.0.69", - "time 0.3.47", + "time 0.3.55", "tracing", "ucs_cards", "ucs_common_enums", @@ -1370,7 +1434,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ "percent-encoding 2.3.2", - "time 0.3.47", + "time 0.3.55", "version_check 0.9.5", ] @@ -1478,7 +1542,7 @@ dependencies = [ "libc", "mio 0.8.11", "parking_lot", - "signal-hook", + "signal-hook 0.3.18", "signal-hook-mio", "winapi", ] @@ -1508,16 +1572,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "darling" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" -dependencies = [ - "darling_core 0.20.11", - "darling_macro 0.20.11", -] - [[package]] name = "darling" version = "0.21.3" @@ -1538,20 +1592,6 @@ dependencies = [ "darling_macro 0.23.0", ] -[[package]] -name = "darling_core" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim 0.11.1", - "syn 2.0.117", -] - [[package]] name = "darling_core" version = "0.21.3" @@ -1579,17 +1619,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "darling_macro" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" -dependencies = [ - "darling_core 0.20.11", - "quote", - "syn 2.0.117", -] - [[package]] name = "darling_macro" version = "0.21.3" @@ -1614,9 +1643,9 @@ dependencies = [ [[package]] name = "dashmap" -version = "6.1.0" +version = "6.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" dependencies = [ "cfg-if", "crossbeam-utils", @@ -1627,55 +1656,54 @@ dependencies = [ ] [[package]] -name = "der" -version = "0.7.10" +name = "defmt" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", + "bitflags 1.3.2", + "defmt-macros", ] [[package]] -name = "deranged" -version = "0.5.8" +name = "defmt-macros" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" dependencies = [ - "powerfmt", - "serde_core", + "defmt-parser", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "derive_builder" -version = "0.20.2" +name = "defmt-parser" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" dependencies = [ - "derive_builder_macro", + "thiserror 2.0.18", ] [[package]] -name = "derive_builder_core" -version = "0.20.2" +name = "der" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ - "darling 0.20.11", - "proc-macro2", - "quote", - "syn 2.0.117", + "const-oid", + "pem-rfc7468", + "zeroize", ] [[package]] -name = "derive_builder_macro" -version = "0.20.2" +name = "deranged" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ - "derive_builder_core", - "syn 2.0.117", + "serde_core", ] [[package]] @@ -1727,7 +1755,7 @@ dependencies = [ "itoa", "pq-sys", "serde_json", - "time 0.3.47", + "time 0.3.55", ] [[package]] @@ -1805,7 +1833,7 @@ dependencies = [ "serde_urlencoded", "strum 0.26.3", "thiserror 1.0.69", - "time 0.3.47", + "time 0.3.55", "tonic 0.14.5", "tracing", "ucs_cards", @@ -1898,7 +1926,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -1942,6 +1970,7 @@ dependencies = [ "injector", "interfaces", "lazy_static", + "log_utils", "mime 0.3.17", "once_cell", "opentelemetry", @@ -1977,15 +2006,6 @@ dependencies = [ "regex", ] -[[package]] -name = "faster-hex" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183" -dependencies = [ - "serde", -] - [[package]] name = "faster-hex" version = "0.10.0" @@ -1998,9 +2018,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" [[package]] name = "fdeflate" @@ -2068,13 +2088,12 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.27" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" dependencies = [ "cfg-if", "libc", - "libredox", ] [[package]] @@ -2386,33 +2405,37 @@ dependencies = [ [[package]] name = "gix" -version = "0.71.0" +version = "0.86.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a61e71ec6817fc3c9f12f812682cfe51ee6ea0d2e27e02fc3849c35524617435" +checksum = "bb3790fd8981cba7949f1ba924ef865d902df731627bc5998d14164063892fce" dependencies = [ "gix-actor", "gix-attributes", "gix-command", "gix-commitgraph", "gix-config", + "gix-credentials", "gix-date", "gix-diff", "gix-dir", "gix-discover", - "gix-features 0.41.1", + "gix-error", + "gix-features", "gix-filter", - "gix-fs 0.14.0", + "gix-fs", "gix-glob", - "gix-hash 0.17.0", + "gix-hash", "gix-hashtable", "gix-ignore", "gix-index", "gix-lock", + "gix-negotiate", "gix-object", "gix-odb", "gix-pack", "gix-path", "gix-pathspec", + "gix-prompt", "gix-protocol", "gix-ref", "gix-refspec", @@ -2424,44 +2447,45 @@ dependencies = [ "gix-submodule", "gix-tempfile", "gix-trace", + "gix-transport", "gix-traverse", "gix-url", - "gix-utils 0.2.0", - "gix-validate 0.9.4", + "gix-utils", + "gix-validate", "gix-worktree", - "once_cell", + "gix-worktree-state", + "gix-worktree-stream", + "gix-zlib", + "nonempty", "parking_lot", - "signal-hook", + "signal-hook 0.4.4", "smallvec", "thiserror 2.0.18", ] [[package]] name = "gix-actor" -version = "0.34.0" +version = "0.41.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f438c87d4028aca4b82f82ba8d8ab1569823cfb3e5bc5fa8456a71678b2a20e7" +checksum = "33f9308ad6fd35b2a865cbe4117ac61b2be59e4a9ef1621c7a9794f7c8e52c5b" dependencies = [ "bstr", "gix-date", - "gix-utils 0.2.0", - "itoa", - "thiserror 2.0.18", - "winnow 0.7.15", + "gix-error", ] [[package]] name = "gix-attributes" -version = "0.25.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4e25825e0430aa11096f8b65ced6780d4a96a133f81904edceebb5344c8dd7f" +checksum = "c31c593692ebdc1e38858d9a2b56f6a594c501e24a38971fe6685571f5a07be0" dependencies = [ "bstr", + "gix-features", "gix-glob", "gix-path", "gix-quote", "gix-trace", - "kstring", "smallvec", "thiserror 2.0.18", "unicode-bom", @@ -2469,27 +2493,27 @@ dependencies = [ [[package]] name = "gix-bitmap" -version = "0.2.16" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d982fc7ef0608e669851d0d2a6141dae74c60d5a27e8daa451f2a4857bbf41e2" +checksum = "7cd1d118d0f5d88b96e6f6e13b566475fef4797ead4a02c26fed36c1375066f7" dependencies = [ - "thiserror 2.0.18", + "gix-error", ] [[package]] name = "gix-chunk" -version = "0.4.12" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c356b3825677cb6ff579551bb8311a81821e184453cbd105e2fc5311b288eeb" +checksum = "b2a871e5cab12ba568845714473505deefffb3c04eb47f4708ce344cd459c1cc" dependencies = [ - "thiserror 2.0.18", + "gix-error", ] [[package]] name = "gix-command" -version = "0.5.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0378995847773a697f8e157fe2963ecf3462fe64be05b7b3da000b3b472def8" +checksum = "cf4363accdf6ef7ba861871d2d521ab7418a04aaaed919fadb022af71d379b12" dependencies = [ "bstr", "gix-path", @@ -2500,43 +2524,42 @@ dependencies = [ [[package]] name = "gix-commitgraph" -version = "0.27.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043cbe49b7a7505150db975f3cb7c15833335ac1e26781f615454d9d640a28fe" +checksum = "a2cd7f054ae2727223fe46dd39c012f066b12f532962d336d29ee193261787da" dependencies = [ "bstr", "gix-chunk", - "gix-hash 0.17.0", + "gix-error", + "gix-hash", "memmap2", - "thiserror 2.0.18", + "nonempty", ] [[package]] name = "gix-config" -version = "0.44.0" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c6f830bf746604940261b49abf7f655d2c19cadc9f4142ae9379e3a316e8cfa" +checksum = "103d11bef95c467577ecfa8b7b86a22e65af3507b2c9bfa3809a4afbae7df301" dependencies = [ "bstr", "gix-config-value", - "gix-features 0.41.1", + "gix-features", "gix-glob", "gix-path", "gix-ref", "gix-sec", - "memchr", - "once_cell", + "gix-utils", "smallvec", "thiserror 2.0.18", "unicode-bom", - "winnow 0.7.15", ] [[package]] name = "gix-config-value" -version = "0.14.12" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dc2c844c4cf141884678cabef736fd91dd73068b9146e6f004ba1a0457944b6" +checksum = "6f6af5321bfd3711a279d6b244d58532ba1cfabf9eb6374791f19929d8970082" dependencies = [ "bitflags 2.11.0", "bstr", @@ -2545,30 +2568,50 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "gix-credentials" +version = "0.39.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbdb1c417980d05d547c19fc2b63344b5257c2387048d69ffe957bda142b59" +dependencies = [ + "bstr", + "gix-command", + "gix-config-value", + "gix-date", + "gix-path", + "gix-prompt", + "gix-quote", + "gix-sec", + "gix-trace", + "gix-url", + "thiserror 2.0.18", +] + [[package]] name = "gix-date" -version = "0.9.4" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daa30058ec7d3511fbc229e4f9e696a35abd07ec5b82e635eff864a2726217e4" +checksum = "7e47b9e8cdc688296609b706428de570f88b1e0eed7156dde7b4a89d26fa4567" dependencies = [ "bstr", + "gix-error", "itoa", "jiff", - "thiserror 2.0.18", ] [[package]] name = "gix-diff" -version = "0.51.0" +version = "0.66.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2c975dad2afc85e4e233f444d1efbe436c3cdcf3a07173984509c436d00a3f8" +checksum = "fee7d89a3c507491cdfc57a1d1e0e300214720b4f7709ebc253e422f99822bfc" dependencies = [ "bstr", "gix-attributes", "gix-command", "gix-filter", - "gix-fs 0.14.0", - "gix-hash 0.17.0", + "gix-fs", + "gix-hash", + "gix-imara-diff", "gix-index", "gix-object", "gix-path", @@ -2577,40 +2620,38 @@ dependencies = [ "gix-trace", "gix-traverse", "gix-worktree", - "imara-diff", "thiserror 2.0.18", ] [[package]] name = "gix-dir" -version = "0.13.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5879497bd3815d8277ed864ec8975290a70de5b62bb92d2d666a4cefc5d4793b" +checksum = "f24bc78f283946ac64757c8a61ffa71f0230aa1e8d98cbb0771db479871dd5b6" dependencies = [ "bstr", "gix-discover", - "gix-fs 0.14.0", + "gix-fs", "gix-ignore", "gix-index", "gix-object", "gix-path", "gix-pathspec", "gix-trace", - "gix-utils 0.2.0", + "gix-utils", "gix-worktree", "thiserror 2.0.18", ] [[package]] name = "gix-discover" -version = "0.39.0" +version = "0.54.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fb8a4349b854506a3915de18d3341e5f1daa6b489c8affc9ca0d69efe86781" +checksum = "b9f517766fa1101dfe2606c1a19a8ffa699099030995a9194445446dfe261bdf" dependencies = [ "bstr", "dunce", - "gix-fs 0.14.0", - "gix-hash 0.17.0", + "gix-fs", "gix-path", "gix-ref", "gix-sec", @@ -2618,136 +2659,107 @@ dependencies = [ ] [[package]] -name = "gix-features" -version = "0.41.1" +name = "gix-error" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016d6050219458d14520fe22bdfdeb9cb71631dec9bc2724767c983f60109634" +checksum = "4a9292309fd944e71b2a3c96d3c03a6feb8852db646febdde7cbb9f79cb5f329" dependencies = [ - "crc32fast", - "flate2", - "gix-path", - "gix-trace", - "gix-utils 0.2.0", - "libc", - "once_cell", - "prodash", - "thiserror 2.0.18", - "walkdir", + "bstr", ] [[package]] name = "gix-features" -version = "0.42.1" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f4399af6ec4fd9db84dd4cf9656c5c785ab492ab40a7c27ea92b4241923fed" +checksum = "20aa09e83a48dc02c5f5f08578aa79d3ab1bab4618b8c362f88684645a02bdcc" dependencies = [ + "bytes", + "crc32fast", + "crossbeam-channel", + "gix-path", "gix-trace", - "gix-utils 0.3.1", + "gix-utils", "libc", + "once_cell", + "parking_lot", "prodash", + "walkdir", ] [[package]] name = "gix-filter" -version = "0.18.0" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb2b2bbffdc5cc9b2b82fc82da1b98163c9b423ac2b45348baa83a947ac9ab89" +checksum = "5e7b5dbf524d97e839f642930c76d7f011c0791e7d11d8148989ac5af7c76aa8" dependencies = [ "bstr", "encoding_rs", "gix-attributes", "gix-command", - "gix-hash 0.17.0", + "gix-hash", "gix-object", - "gix-packetline-blocking", + "gix-packetline", "gix-path", "gix-quote", "gix-trace", - "gix-utils 0.2.0", + "gix-utils", "smallvec", "thiserror 2.0.18", ] [[package]] name = "gix-fs" -version = "0.14.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "951e886120dc5fa8cac053e5e5c89443f12368ca36811b2e43d1539081f9c111" +checksum = "865cf13fcaf5455220546cb9607c416bd1be9a6caafd143655a362fdeab64e80" dependencies = [ "bstr", - "fastrand", - "gix-features 0.41.1", + "gix-features", "gix-path", - "gix-utils 0.2.0", - "thiserror 2.0.18", -] - -[[package]] -name = "gix-fs" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a0637149b4ef24d3ea55f81f77231401c8463fae6da27331c987957eb597c7" -dependencies = [ - "bstr", - "fastrand", - "gix-features 0.42.1", - "gix-path", - "gix-utils 0.3.1", + "gix-utils", "thiserror 2.0.18", ] [[package]] name = "gix-glob" -version = "0.19.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20972499c03473e773a2099e5fd0c695b9b72465837797a51a43391a1635a030" +checksum = "421e92a711554fa5827d1b0599d3389acdd0f6729e97a8c5a57d79af1e50bf36" dependencies = [ "bitflags 2.11.0", "bstr", - "gix-features 0.41.1", + "gix-features", "gix-path", ] [[package]] name = "gix-hash" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "834e79722063958b03342edaa1e17595cd2939bb2b3306b3225d0815566dcb49" -dependencies = [ - "faster-hex 0.9.0", - "gix-features 0.41.1", - "sha1-checked", - "thiserror 2.0.18", -] - -[[package]] -name = "gix-hash" -version = "0.18.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d4900562c662852a6b42e2ef03442eccebf24f047d8eab4f23bc12ef0d785d8" +checksum = "13adaa73415fd6c902310923f68d0b98e8cecf14b33ea58c02cc387cee56f54e" dependencies = [ - "faster-hex 0.10.0", - "gix-features 0.42.1", + "faster-hex", + "gix-features", "sha1-checked", "thiserror 2.0.18", ] [[package]] name = "gix-hashtable" -version = "0.8.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b5cb3c308b4144f2612ff64e32130e641279fcf1a84d8d40dad843b4f64904" +checksum = "78fccd6fea3bcf0b39c076bae60ae49b08daaf538b950202101a981f9d3c01d3" dependencies = [ - "gix-hash 0.18.0", - "hashbrown 0.14.5", + "gix-hash", + "hashbrown 0.17.1", "parking_lot", ] [[package]] name = "gix-ignore" -version = "0.14.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a27c8380f493a10d1457f756a3f81924d578fc08d6535e304dfcafbf0261d18" +checksum = "12cff8e8aa125e39377456073e63df3334d9e5741372ddcc226198015076dda2" dependencies = [ "bstr", "gix-glob", @@ -2756,82 +2768,105 @@ dependencies = [ "unicode-bom", ] +[[package]] +name = "gix-imara-diff" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a791e6620676a875f362f3156ed213e73ca099a09bf992c18812abe65cc37b1" +dependencies = [ + "bstr", + "hashbrown 0.16.1", +] + [[package]] name = "gix-index" -version = "0.39.0" +version = "0.54.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "855bece2d4153453aa5d0a80d51deea1ce8cd6a3b4cf213da85ac344ccb908a7" +checksum = "5009c4e7e9f9b4cfaaab1153e49133eb04d79c015b5702d6c3d2ab94271a89c6" dependencies = [ "bitflags 2.11.0", "bstr", "filetime", "fnv", "gix-bitmap", - "gix-features 0.41.1", - "gix-fs 0.14.0", - "gix-hash 0.17.0", + "gix-features", + "gix-fs", + "gix-hash", "gix-lock", "gix-object", "gix-traverse", - "gix-utils 0.2.0", - "gix-validate 0.9.4", - "hashbrown 0.14.5", + "gix-utils", + "gix-validate", + "hashbrown 0.17.1", "itoa", "libc", "memmap2", - "rustix 0.38.44", + "rustix 1.1.4", "smallvec", "thiserror 2.0.18", ] [[package]] name = "gix-lock" -version = "17.1.0" +version = "24.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "570f8b034659f256366dc90f1a24924902f20acccd6a15be96d44d1269e7a796" +checksum = "d4c69157820343bf1c6e4b88b9808e920900de02e18aaf5862b30ada43814848" dependencies = [ "gix-tempfile", - "gix-utils 0.3.1", + "gix-utils", "thiserror 2.0.18", ] +[[package]] +name = "gix-negotiate" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fa5aa789990f124e4f559b142cecfb60662cb4ae17006684ea9d668f4f07689" +dependencies = [ + "bitflags 2.11.0", + "gix-commitgraph", + "gix-date", + "gix-hash", + "gix-object", + "gix-revwalk", +] + [[package]] name = "gix-object" -version = "0.48.0" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4943fcdae6ffc135920c9ea71e0362ed539182924ab7a85dd9dac8d89b0dd69a" +checksum = "0e48c235e7f886eb819fc878af75be889333dd3c38bee02ed7af48ae2cf596c4" dependencies = [ "bstr", "gix-actor", "gix-date", - "gix-features 0.41.1", - "gix-hash 0.17.0", + "gix-features", + "gix-hash", "gix-hashtable", - "gix-path", - "gix-utils 0.2.0", - "gix-validate 0.9.4", + "gix-utils", + "gix-validate", "itoa", "smallvec", "thiserror 2.0.18", - "winnow 0.7.15", ] [[package]] name = "gix-odb" -version = "0.68.0" +version = "0.83.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50306d40dcc982eb6b7593103f066ea6289c7b094cb9db14f3cd2be0b9f5e610" +checksum = "8dd494ffb5037e62b8220109e894d2861ff2150a2cacbfccdba57ae1ebab2b96" dependencies = [ "arc-swap", - "gix-date", - "gix-features 0.41.1", - "gix-fs 0.14.0", - "gix-hash 0.17.0", + "gix-features", + "gix-fs", + "gix-hash", "gix-hashtable", "gix-object", "gix-pack", "gix-path", "gix-quote", + "gix-zlib", + "memmap2", "parking_lot", "tempfile", "thiserror 2.0.18", @@ -2839,63 +2874,55 @@ dependencies = [ [[package]] name = "gix-pack" -version = "0.58.0" +version = "0.73.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b65fffb09393c26624ca408d32cfe8776fb94cd0a5cdf984905e1d2f39779cb" +checksum = "6d5446127b269706e85998065267ddd2ccc3550179da6780b22fe496175ccb20" dependencies = [ "clru", "gix-chunk", - "gix-features 0.41.1", - "gix-hash 0.17.0", + "gix-error", + "gix-features", + "gix-hash", "gix-hashtable", "gix-object", "gix-path", + "gix-tempfile", + "gix-zlib", "memmap2", + "parking_lot", "smallvec", "thiserror 2.0.18", ] [[package]] name = "gix-packetline" -version = "0.18.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "123844a70cf4d5352441dc06bab0da8aef61be94ec239cb631e0ba01dc6d3a04" -dependencies = [ - "bstr", - "faster-hex 0.9.0", - "gix-trace", - "thiserror 2.0.18", -] - -[[package]] -name = "gix-packetline-blocking" -version = "0.18.3" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ecf3ea2e105c7e45587bac04099824301262a6c43357fad5205da36dbb233b3" +checksum = "3766025c72319c4accdd854a18e6f0dd176c8eb0f3bc8a60a7765be2b50cabf2" dependencies = [ "bstr", - "faster-hex 0.9.0", + "faster-hex", "gix-trace", "thiserror 2.0.18", ] [[package]] name = "gix-path" -version = "0.10.22" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cb06c3e4f8eed6e24fd915fa93145e28a511f4ea0e768bae16673e05ed3f366" +checksum = "751d6bd162106f8c1e7e9aaccb5bbdd605267e91a930a17a4560c46e33a9100c" dependencies = [ "bstr", "gix-trace", - "gix-validate 0.10.1", + "gix-validate", "thiserror 2.0.18", ] [[package]] name = "gix-pathspec" -version = "0.10.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef8422c3c9066d649074b24025125963f85232bfad32d6d16aea9453b82ec14" +checksum = "49f6fa5f8007f008187c3f60b4373209ca83d1cc947f35ede03e16cd15a4d137" dependencies = [ "bitflags 2.11.0", "bstr", @@ -2906,98 +2933,121 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "gix-prompt" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cb1f1eb92d6f4c9d4c105a6ca912cff637fbd5cacbcbafa5deccd88bfaa3565" +dependencies = [ + "gix-command", + "gix-config-value", + "parking_lot", + "rustix 1.1.4", + "thiserror 2.0.18", +] + [[package]] name = "gix-protocol" -version = "0.49.0" +version = "0.64.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5678ddae1d62880bc30e2200be1b9387af3372e0e88e21f81b4e7f8367355b5a" +checksum = "dede40e89c1e90f548415f50636bb051f6d9c60f68b8b710bc07825722d19588" dependencies = [ + "bisync", "bstr", + "gix-credentials", "gix-date", - "gix-features 0.41.1", - "gix-hash 0.17.0", + "gix-features", + "gix-hash", + "gix-lock", + "gix-negotiate", + "gix-object", "gix-ref", + "gix-refspec", + "gix-revwalk", "gix-shallow", + "gix-trace", "gix-transport", - "gix-utils 0.2.0", - "maybe-async", + "gix-utils", + "nonempty", "thiserror 2.0.18", - "winnow 0.7.15", ] [[package]] name = "gix-quote" -version = "0.5.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b005c550bf84de3b24aa5e540a23e6146a1c01c7d30470e35d75a12f827f969" +checksum = "a6e541fc33cc2b783b7979040d445a0c86a2eca747c8faea4ca84230d06ae6ef" dependencies = [ "bstr", - "gix-utils 0.2.0", - "thiserror 2.0.18", + "gix-error", + "gix-utils", ] [[package]] name = "gix-ref" -version = "0.51.0" +version = "0.66.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2e1f7eb6b7ce82d2d19961f74bd637bab3ea79b1bc7bfb23dbefc67b0415d8b" +checksum = "eeb0c90a8f6202ceaaa22996cbf837c943ccb2d8af9ff3490f0758305e6b7883" dependencies = [ "gix-actor", - "gix-features 0.41.1", - "gix-fs 0.14.0", - "gix-hash 0.17.0", + "gix-features", + "gix-fs", + "gix-hash", "gix-lock", "gix-object", "gix-path", "gix-tempfile", - "gix-utils 0.2.0", - "gix-validate 0.9.4", + "gix-utils", + "gix-validate", "memmap2", "thiserror 2.0.18", - "winnow 0.7.15", ] [[package]] name = "gix-refspec" -version = "0.29.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d8587b21e2264a6e8938d940c5c99662779c13a10741a5737b15fc85c252ffc" +checksum = "7406282cc0259b51f6aee299ca3d31279a020530363152a2e6c96e8a7f7bbc83" dependencies = [ "bstr", - "gix-hash 0.17.0", + "gix-error", + "gix-glob", + "gix-hash", "gix-revision", - "gix-validate 0.9.4", + "gix-validate", "smallvec", "thiserror 2.0.18", ] [[package]] name = "gix-revision" -version = "0.33.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "342caa4e158df3020cadf62f656307c3948fe4eacfdf67171d7212811860c3e9" +checksum = "e55e09d4a1ecf2beecc8c09cafcad37979e805b31f588b0e957e191df5783681" dependencies = [ "bitflags 2.11.0", "bstr", "gix-commitgraph", "gix-date", - "gix-hash 0.17.0", + "gix-error", + "gix-hash", "gix-hashtable", "gix-object", "gix-revwalk", "gix-trace", - "thiserror 2.0.18", + "nonempty", ] [[package]] name = "gix-revwalk" -version = "0.19.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dc7c3d7e5cdc1ab8d35130106e4af0a4f9f9eca0c81f4312b690780e92bde0d" +checksum = "36c113c0a53294dc6280ffc06cbcc4f50f820397e97d6a00b429a44b8db26e29" dependencies = [ "gix-commitgraph", "gix-date", - "gix-hash 0.17.0", + "gix-error", + "gix-hash", "gix-hashtable", "gix-object", "smallvec", @@ -3006,56 +3056,59 @@ dependencies = [ [[package]] name = "gix-sec" -version = "0.10.12" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47aeb0f13de9ef2f3033f5ff218de30f44db827ac9f1286f9ef050aacddd5888" +checksum = "af4fe6c152c1d50aea36f299825702cd37e303307832fec1d0fdd5844e47ce2f" dependencies = [ "bitflags 2.11.0", "gix-path", "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "gix-shallow" -version = "0.3.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc0598aacfe1d52575a21c9492fee086edbb21e228ec36c819c42ab923f434c3" +checksum = "1ecc9f4b40537043e4bbd7d3d1760e74fb8e7b07a546166b558acaa73ad97f4a" dependencies = [ "bstr", - "gix-hash 0.17.0", + "gix-hash", "gix-lock", + "nonempty", "thiserror 2.0.18", ] [[package]] name = "gix-status" -version = "0.18.0" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "605a6d0eb5891680c46e24b2ee7a63ef7bd39cb136dc7c7e55172960cf68b2f5" +checksum = "5f83b1c74e69b90411fbfe89ccebc47aa49457ce4eb9b942b1311258fc863d22" dependencies = [ "bstr", "filetime", "gix-diff", "gix-dir", - "gix-features 0.41.1", + "gix-features", "gix-filter", - "gix-fs 0.14.0", - "gix-hash 0.17.0", + "gix-fs", + "gix-hash", "gix-index", "gix-object", "gix-path", "gix-pathspec", "gix-worktree", + "hashbrown 0.16.1", "portable-atomic", "thiserror 2.0.18", + "windows-sys 0.61.2", ] [[package]] name = "gix-submodule" -version = "0.18.0" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c7390c2059505c365e9548016d4edc9f35749c6a9112b7b1214400bbc68da2" +checksum = "5fd98077a56d08886112e6b08dc94076d03539f4bc0b9d7880e4be2b8a640d8c" dependencies = [ "bstr", "gix-config", @@ -3068,36 +3121,36 @@ dependencies = [ [[package]] name = "gix-tempfile" -version = "17.1.0" +version = "24.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c750e8c008453a2dba67a2b0d928b7716e05da31173a3f5e351d5457ad4470aa" +checksum = "b675b920bd5a61d17ad542772f03ec34c60feb8ff683e1560c03ae967363731e" dependencies = [ "dashmap", - "gix-fs 0.15.0", + "gix-fs", "libc", - "once_cell", "parking_lot", - "signal-hook", + "signal-hook 0.4.4", "signal-hook-registry", "tempfile", ] [[package]] name = "gix-trace" -version = "0.1.18" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f69a13643b8437d4ca6845e08143e847a36ca82903eed13303475d0ae8b162e0" +checksum = "be3eb81d9dc914335923e50d52829c551feefd6a72d176c4130c546b67a60814" [[package]] name = "gix-transport" -version = "0.46.0" +version = "0.58.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3f68c2870bfca8278389d2484a7f2215b67d0b0cc5277d3c72ad72acf41787e" +checksum = "3f36d045b840f8aeee1a527e677eab1fbebfbbe94bf2e708fa81d0b4b742d5fc" dependencies = [ "bstr", "gix-command", - "gix-features 0.41.1", + "gix-features", "gix-packetline", + "gix-path", "gix-quote", "gix-sec", "gix-url", @@ -3106,14 +3159,14 @@ dependencies = [ [[package]] name = "gix-traverse" -version = "0.45.0" +version = "0.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c0b049f8bdb61b20016694102f7b507f2e1727e83e9c5e6dad4f7d84ff7384" +checksum = "008c5cd879e46e86b5c2469e633611978b18775d53d05668d691bc13088bd409" dependencies = [ "bitflags 2.11.0", "gix-commitgraph", "gix-date", - "gix-hash 0.17.0", + "gix-hash", "gix-hashtable", "gix-object", "gix-revwalk", @@ -3123,76 +3176,100 @@ dependencies = [ [[package]] name = "gix-url" -version = "0.30.0" +version = "0.37.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48dfe23f93f1ddb84977d80bb0dd7aa09d1bf5d5afc0c9b6820cccacc25ae860" +checksum = "31bdfc93aa880cda3272718a5879ce3aa7723fa13514320dd6608151607afe72" dependencies = [ "bstr", - "gix-features 0.41.1", "gix-path", + "gix-utils", "percent-encoding 2.3.2", "thiserror 2.0.18", - "url 2.5.8", ] [[package]] name = "gix-utils" -version = "0.2.0" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "189f8724cf903e7fd57cfe0b7bc209db255cacdcb22c781a022f52c3a774f8d0" +checksum = "b1795bd2a970ca8b2185318c2abb97d955c71992f1cf28de73ad3b593a9f3ce8" dependencies = [ "bstr", "fastrand", + "getrandom 0.4.2", "unicode-normalization", ] [[package]] -name = "gix-utils" -version = "0.3.1" +name = "gix-validate" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "befcdbdfb1238d2854591f760a48711bed85e72d80a10e8f2f93f656746ef7c5" +checksum = "9a034e84d1e04e1b1f20f51f12491da230b6ac8b925d0c8e1b89bcd87a7c5ccc" dependencies = [ - "fastrand", - "unicode-normalization", + "bstr", ] [[package]] -name = "gix-validate" -version = "0.9.4" +name = "gix-worktree" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34b5f1253109da6c79ed7cf6e1e38437080bb6d704c76af14c93e2f255234084" +checksum = "31eb8e675122e83585e461fe28f68ff8c5ed55b49017b697e7e76423ff973424" dependencies = [ "bstr", - "thiserror 2.0.18", + "gix-attributes", + "gix-fs", + "gix-glob", + "gix-hash", + "gix-ignore", + "gix-index", + "gix-object", + "gix-path", + "gix-validate", ] [[package]] -name = "gix-validate" -version = "0.10.1" +name = "gix-worktree-state" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b1e63a5b516e970a594f870ed4571a8fdcb8a344e7bd407a20db8bd61dbfde4" +checksum = "bc47372fc12b9fbea51b257bcc8d4970326cf5c7e42135bbfb5d72871bb30bd4" dependencies = [ "bstr", + "gix-features", + "gix-filter", + "gix-fs", + "gix-index", + "gix-object", + "gix-path", + "gix-worktree", + "io-close", "thiserror 2.0.18", ] [[package]] -name = "gix-worktree" -version = "0.40.0" +name = "gix-worktree-stream" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7760dbc4b79aa274fed30adc0d41dca6b917641f26e7867c4071b1fb4dc727b" +checksum = "3b088c8724e7be120c4798dd86925cf05332c9d356a463542578600c50c7a549" dependencies = [ - "bstr", "gix-attributes", - "gix-features 0.41.1", - "gix-fs 0.14.0", - "gix-glob", - "gix-hash 0.17.0", - "gix-ignore", - "gix-index", + "gix-error", + "gix-features", + "gix-filter", + "gix-fs", + "gix-hash", "gix-object", "gix-path", - "gix-validate 0.9.4", + "gix-traverse", + "parking_lot", +] + +[[package]] +name = "gix-zlib" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e8813f5579b3075ff9c90f7c59cd2b62b4ebb639361f0911648b22d7446cc7c" +dependencies = [ + "thiserror 2.0.18", + "zlib-rs", ] [[package]] @@ -3284,7 +3361,7 @@ dependencies = [ "prost-types", "rand 0.8.5", "reqwest 0.11.27", - "rustc-hash 2.1.1", + "rustc-hash 2.1.3", "serde", "serde_json", "serde_path_to_error", @@ -3292,7 +3369,7 @@ dependencies = [ "superposition_core", "tempfile", "thiserror 1.0.69", - "time 0.3.47", + "time 0.3.55", "tokio", "tokio-stream", "tonic 0.14.5", @@ -3407,6 +3484,17 @@ dependencies = [ "foldhash 0.2.0", ] +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + [[package]] name = "hashlink" version = "0.8.4" @@ -3798,7 +3886,7 @@ dependencies = [ "serde", "serde_json", "subtle", - "time 0.3.47", + "time 0.3.55", "url 2.5.8", "zeroize", ] @@ -3965,15 +4053,6 @@ dependencies = [ "png", ] -[[package]] -name = "imara-diff" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17d34b7d42178945f775e84bc4c36dde7c1c6cdfea656d3354d009056f2bb3d2" -dependencies = [ - "hashbrown 0.15.5", -] - [[package]] name = "impl-more" version = "0.1.9" @@ -4099,7 +4178,7 @@ dependencies = [ "serde_json", "strum 0.26.3", "thiserror 1.0.69", - "time 0.3.47", + "time 0.3.55", "tracing", "tracing-actix-web", "ucs_common_enums", @@ -4107,6 +4186,16 @@ dependencies = [ "utoipa", ] +[[package]] +name = "io-close" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cadcf447f06744f8ce713d2d6239bb5bde2c357a452397a9ed90c625da390bc" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "ipnet" version = "2.12.0" @@ -4180,25 +4269,37 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.23" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" +checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc" dependencies = [ + "defmt", + "jiff-core", "jiff-static", "jiff-tzdb-platform", "log 0.4.29", "portable-atomic", "portable-atomic-util", "serde_core", - "windows-sys 0.61.2", + "windows-link", +] + +[[package]] +name = "jiff-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09" +dependencies = [ + "defmt", ] [[package]] name = "jiff-static" -version = "0.2.23" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" +checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204" dependencies = [ + "jiff-core", "proc-macro2", "quote", "syn 2.0.117", @@ -4244,7 +4345,7 @@ dependencies = [ "serde", "serde_json", "thiserror 1.0.69", - "time 0.3.47", + "time 0.3.55", ] [[package]] @@ -4293,7 +4394,7 @@ dependencies = [ "reqwest 0.11.27", "serde", "serde_json", - "time 0.3.47", + "time 0.3.55", "url 2.5.8", "uuid", ] @@ -4345,15 +4446,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "kstring" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "558bf9508a558512042d3095138b1f7b8fe90c5467d94f9f1da28b3731c5dbd1" -dependencies = [ - "static_assertions", -] - [[package]] name = "language-tags" version = "0.2.2" @@ -4383,9 +4475,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.183" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libm" @@ -4393,18 +4485,6 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" -[[package]] -name = "libredox" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" -dependencies = [ - "bitflags 2.11.0", - "libc", - "plain", - "redox_syscall 0.7.3", -] - [[package]] name = "libz-sys" version = "1.1.25" @@ -4489,14 +4569,14 @@ dependencies = [ [[package]] name = "log_utils" version = "0.1.0" -source = "git+https://github.com/juspay/framework-libs-rs?rev=243562041252fe5897ce888d20b715ffdc3767ce#243562041252fe5897ce888d20b715ffdc3767ce" +source = "git+https://github.com/juspay/framework-libs-rs?rev=5d0b5824b2a82dafae492f9930c95060507d91b4#5d0b5824b2a82dafae492f9930c95060507d91b4" dependencies = [ "gethostname 1.1.0", - "rustc-hash 2.1.1", + "rustc-hash 2.1.3", "serde", "serde_json", "thiserror 2.0.18", - "time 0.3.47", + "time 0.3.55", "tracing", "tracing-appender", "tracing-subscriber", @@ -4570,17 +4650,6 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" -[[package]] -name = "maybe-async" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "md5" version = "0.7.0" @@ -4595,9 +4664,9 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memmap2" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" dependencies = [ "libc", ] @@ -4820,13 +4889,19 @@ dependencies = [ "memchr", ] +[[package]] +name = "nonempty" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9737e026353e5cd0736f98eddae28665118eb6f6600902a7f50db585621fecb6" + [[package]] name = "nu-ansi-term" version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -4886,9 +4961,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-integer" @@ -5176,7 +5251,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.18", + "redox_syscall", "smallvec", "windows-link", ] @@ -5614,11 +5689,10 @@ dependencies = [ [[package]] name = "prodash" -version = "29.0.2" +version = "31.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04bb108f648884c23b98a0e940ebc2c93c0c3b89f04dbaf7eb8256ce617d1bc" +checksum = "962200e2d7d551451297d9fdce85138374019ada198e30ea9ede38034e27604c" dependencies = [ - "log 0.4.29", "parking_lot", ] @@ -5682,7 +5756,7 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" dependencies = [ - "heck 0.5.0", + "heck 0.4.1", "itertools 0.14.0", "log 0.4.29", "multimap", @@ -5829,7 +5903,7 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.1.1", + "rustc-hash 2.1.3", "rustls 0.23.37", "socket2 0.6.3", "thiserror 2.0.18", @@ -5849,7 +5923,7 @@ dependencies = [ "lru-slab", "rand 0.9.2", "ring", - "rustc-hash 2.1.1", + "rustc-hash 2.1.3", "rustls 0.23.37", "rustls-pki-types", "slab", @@ -6142,15 +6216,6 @@ dependencies = [ "bitflags 2.11.0", ] -[[package]] -name = "redox_syscall" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" -dependencies = [ - "bitflags 2.11.0", -] - [[package]] name = "ref-cast" version = "1.0.25" @@ -6415,7 +6480,7 @@ dependencies = [ "serde_json", "serde_path_to_error", "strum 0.26.3", - "time 0.3.47", + "time 0.3.55", "tokio", "tracing", "tracing-actix-web", @@ -6488,9 +6553,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] name = "rustc_version" @@ -6524,7 +6589,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -6595,9 +6660,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] name = "rusty-fork" @@ -6877,7 +6942,7 @@ dependencies = [ "serde_core", "serde_json", "serde_with_macros", - "time 0.3.47", + "time 0.3.55", ] [[package]] @@ -6999,6 +7064,16 @@ dependencies = [ "signal-hook-registry", ] +[[package]] +name = "signal-hook" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a0c28ca5908dbdbcd52e6fdaa00358ab88637f8ab33e1f188dd510eb44b53d" +dependencies = [ + "libc", + "signal-hook-registry", +] + [[package]] name = "signal-hook-mio" version = "0.2.5" @@ -7007,7 +7082,7 @@ checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" dependencies = [ "libc", "mio 0.8.11", - "signal-hook", + "signal-hook 0.3.18", ] [[package]] @@ -7051,7 +7126,7 @@ dependencies = [ "num-bigint", "num-traits", "thiserror 2.0.18", - "time 0.3.47", + "time 0.3.55", ] [[package]] @@ -7129,7 +7204,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -7397,6 +7472,12 @@ dependencies = [ "sval_nested", ] +[[package]] +name = "symlink" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" + [[package]] name = "syn" version = "1.0.109" @@ -7503,7 +7584,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix 1.1.4", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -7597,12 +7678,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.47" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" dependencies = [ "deranged", - "itoa", "libc", "num-conv", "num_threads", @@ -7614,15 +7694,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.27" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" dependencies = [ "num-conv", "time-core", @@ -8050,13 +8130,14 @@ dependencies = [ [[package]] name = "tracing-appender" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" +checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" dependencies = [ "crossbeam-channel", + "symlink", "thiserror 2.0.18", - "time 0.3.47", + "time 0.3.55", "tracing-subscriber", ] @@ -8221,7 +8302,7 @@ dependencies = [ "serde", "serde_json", "thiserror 1.0.69", - "time 0.3.47", + "time 0.3.55", "ucs_common_utils", ] @@ -8252,6 +8333,7 @@ dependencies = [ "http 1.4.0", "hyperswitch_masking", "josekit", + "log_utils", "md5", "multipart", "nanoid", @@ -8272,7 +8354,7 @@ dependencies = [ "superposition_core", "superposition_types", "thiserror 1.0.69", - "time 0.3.47", + "time 0.3.55", "tonic 0.14.5", "tracing", "tracing-kafka", @@ -8307,7 +8389,7 @@ dependencies = [ "serde_path_to_error", "strum 0.26.3", "thiserror 1.0.69", - "time 0.3.47", + "time 0.3.55", "tokio", "tonic 0.14.5", "tonic-reflection", @@ -8710,43 +8792,41 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vergen" -version = "9.0.6" +version = "10.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b2bf58be11fc9414104c6d3a2e464163db5ef74b12296bda593cac37b6e4777" +checksum = "3fd02b50246a773d6b8f48cf8e4e623a2773fd31cbe697bf10289abbb17e9e87" dependencies = [ "anyhow", - "cargo_metadata 0.19.2", - "derive_builder", - "regex", + "bon", "rustc_version", "rustversion", - "time 0.3.47", + "time 0.3.55", "vergen-lib", ] [[package]] name = "vergen-gix" -version = "1.0.9" +version = "10.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f8dfe6eb333a1397e596164ae7326f68e4b95267b41aedc6aa3c81f3426a010" +checksum = "3a3a1d6008b29a3163c509b206109fea7e79cb8de43ef1468b7d3c29471f3751" dependencies = [ "anyhow", - "derive_builder", + "bon", "gix", "rustversion", - "time 0.3.47", + "time 0.3.55", "vergen", "vergen-lib", ] [[package]] name = "vergen-lib" -version = "0.1.6" +version = "10.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b07e6010c0f3e59fcb164e0163834597da68d1f864e2b8ca49f74de01e9c166" +checksum = "7d6a4e23de0fd3f8a940650a4d83eca980e9ef8f108fe6ddeb5313b0313523d3" dependencies = [ "anyhow", - "derive_builder", + "bon", "rustversion", ] @@ -8992,7 +9072,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] @@ -9556,6 +9636,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "zlib-rs" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" + [[package]] name = "zmij" version = "1.0.21" diff --git a/crates/common/common_utils/Cargo.toml b/crates/common/common_utils/Cargo.toml index f4608e8f33..fa4ea11414 100644 --- a/crates/common/common_utils/Cargo.toml +++ b/crates/common/common_utils/Cargo.toml @@ -12,6 +12,7 @@ anyhow = "1.0" rdkafka = { version = "0.36", optional = true } tracing-kafka = { path = "../tracing-kafka", optional = true } tracing = { workspace = true } +log_utils = { git = "https://github.com/juspay/framework-libs-rs", rev = "5d0b5824b2a82dafae492f9930c95060507d91b4", package = "log_utils", features = ["tracing", "tracing-storage-api"], optional = true } once_cell = "1.19" chrono = "0.4.31" @@ -52,6 +53,7 @@ default = [] kafka = ["dep:rdkafka", "dep:tracing-kafka"] async_ext = ["dep:async-trait", "dep:futures"] superposition = ["dep:superposition_core", "dep:superposition_types"] +log-transformations = ["dep:log_utils"] [dependencies.async-trait] version = "0.1.74" diff --git a/crates/common/common_utils/src/events.rs b/crates/common/common_utils/src/events.rs index e9bc9c1a5f..cfdf456504 100644 --- a/crates/common/common_utils/src/events.rs +++ b/crates/common/common_utils/src/events.rs @@ -474,6 +474,197 @@ pub struct KafkaTopicConfig { pub partition_key_field: String, } +/// Controls whether transformations copy or replace the original source fields. +#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum TransformationMode { + /// Add the new key but keep the original source field (default). + #[default] + Copy, + /// Add the new key and remove the original source field after all transformations complete. + Replace, +} + +/// A single log transformation with pre-split target path segments. +#[cfg(feature = "log-transformations")] +#[derive(Debug, Clone)] +pub struct CompiledLogTransformation { + /// Source field key in span storage (e.g., "gateway", "request.url"). + /// Leaked to `&'static str` at startup for `Storage::record_value()`. + pub source_field: &'static str, + /// Pre-split target path segments (e.g., `["api_details", "url"]`). + /// Segment 0 is the root key; segments 1.. are the nested sub-path. + pub target_segments: Vec, + /// Top-level key for `Storage::record_value()` — leaked `&'static str`. + pub target_root_key: &'static str, +} + +/// Pre-compiled transformations for one golden log line type (incoming or outgoing). +#[cfg(feature = "log-transformations")] +#[derive(Debug, Clone, Default)] +pub struct CompiledLogTransformations { + pub rules: Vec, +} + +#[cfg(feature = "log-transformations")] +impl CompiledLogTransformations { + /// Compile raw `target_path → source_field` mappings into pre-split rules. + /// Uses `Box::leak` to produce `&'static str` keys — safe because config is loaded once + /// at process startup and lives for the entire process lifetime. + pub fn compile(raw: &HashMap) -> Self { + let rules = raw + .iter() + .map(|(target_path, source_field)| { + let segments: Vec = + target_path.split('.').map(String::from).collect(); + let root_key: &'static str = + Box::leak(segments[0].clone().into_boxed_str()); + let source_key: &'static str = + Box::leak(source_field.clone().into_boxed_str()); + CompiledLogTransformation { + source_field: source_key, + target_segments: segments, + target_root_key: root_key, + } + }) + .collect(); + Self { rules } + } + + /// Returns `true` if no transformation rules are configured. + pub fn is_empty(&self) -> bool { + self.rules.is_empty() + } +} + +/// Holds compiled transformations for both golden log lines. +#[cfg(feature = "log-transformations")] +#[derive(Debug, Clone, Default)] +pub struct LogTransformationConfig { + pub incoming: CompiledLogTransformations, + pub outgoing: CompiledLogTransformations, + pub mode: TransformationMode, +} + +#[cfg(feature = "log-transformations")] +impl LogTransformationConfig { + pub fn compile( + incoming: &HashMap, + outgoing: &HashMap, + mode: TransformationMode, + ) -> Self { + Self { + incoming: CompiledLogTransformations::compile(incoming), + outgoing: CompiledLogTransformations::compile(outgoing), + mode, + } + } +} + +/// Given span storage values and compiled transformation rules, compute the new key-value +/// pairs to write back into span storage. Returns a list of `(root_key, value)` pairs. +/// +/// For simple renames (1 segment), the value is copied directly. +/// For nested targets (2+ segments), values are grouped under the root key as a nested JSON object +/// using `set_nested_value_from_segments()`. +#[cfg(feature = "log-transformations")] +pub fn compute_log_transformations( + storage_values: &HashMap, + compiled: &CompiledLogTransformations, +) -> Vec<(&'static str, serde_json::Value)> { + let mut writes: HashMap<&'static str, serde_json::Value> = HashMap::new(); + + for rule in &compiled.rules { + let source_value = match storage_values.get(rule.source_field) { + Some(v) => v.clone(), + None => continue, + }; + + if rule.target_segments.len() == 1 { + // Simple rename: "connector" = "gateway" + writes.insert(rule.target_root_key, source_value); + } else { + // Nested target: "api_details.url" = "request.url" + let root = writes + .entry(rule.target_root_key) + .or_insert_with(|| serde_json::json!({})); + // Walk pre-compiled segments[1..] to set the value + set_nested_value_from_segments(root, &rule.target_segments[1..], source_value); + } + } + + writes.into_iter().collect() +} + +/// Like `set_nested_value` but takes pre-split path segments instead of a dotted string. +/// Avoids runtime `split('.')` — segments are pre-compiled at startup. +#[cfg(feature = "log-transformations")] +fn set_nested_value_from_segments( + target: &mut serde_json::Value, + segments: &[String], + value: serde_json::Value, +) { + if segments.is_empty() { + return; + } + if segments.len() == 1 { + target[&segments[0]] = value; + return; + } + // Walk intermediate segments, creating objects as needed + let mut current = target; + for (i, segment) in segments.iter().enumerate() { + if i == segments.len() - 1 { + current[segment.as_str()] = value; + return; + } + if !current[segment.as_str()].is_object() { + current[segment.as_str()] = serde_json::json!({}); + } + current = &mut current[segment.as_str()]; + } +} + +/// Apply compiled log transformation rules to the current span's storage. +/// Snapshots the current span storage, computes new fields from the rules, +/// and writes them back. +#[cfg(feature = "log-transformations")] +pub fn apply_log_transformations_to_span(compiled: &CompiledLogTransformations) { + if compiled.is_empty() { + return; + } + let snapshot: Option> = + log_utils::Storage::with_current_span(|storage| storage.values().clone()); + let Some(snapshot) = snapshot else { + return; + }; + let writes = compute_log_transformations(&snapshot, compiled); + // Filter out reserved keys BEFORE entering the span lock. + // `record_value` calls `tracing::warn!` for reserved keys, which re-enters + // the subscriber and deadlocks when called inside `with_current_span_mut`. + let writes: Vec<_> = writes + .into_iter() + .filter(|(key, _)| { + if log_utils::Storage::is_reserved(key) { + tracing::warn!( + "Log transformation target key `{key}` is reserved by the logging infrastructure, skipping" + ); + false + } else { + true + } + }) + .collect(); + if writes.is_empty() { + return; + } + log_utils::Storage::with_current_span_mut(|storage| { + for (key, value) in writes { + storage.record_value(key, value); + } + }); +} + /// Configuration for events system #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, config_patch_derive::Patch)] pub struct EventConfig { @@ -484,6 +675,9 @@ pub struct EventConfig { /// Inbound request events. pub api_events: KafkaTopicConfig, #[serde(default)] + #[patch(ignore)] + pub transformation_mode: TransformationMode, + #[serde(default)] pub transformations: HashMap, // target_path → source_field #[serde(default)] pub static_values: HashMap, // target_path → static_value @@ -508,6 +702,7 @@ impl Default for EventConfig { brokers: vec!["localhost:9092".to_string()], connector_events: KafkaTopicConfig::default(), api_events: KafkaTopicConfig::default(), + transformation_mode: TransformationMode::default(), transformations: HashMap::new(), static_values: HashMap::new(), extractions: HashMap::new(), @@ -555,9 +750,11 @@ pub(crate) fn process_event_with_config( let normalize_path = |path: &str| -> String { path.replace("_DOT_", ".").replace("_dot_", ".") }; - // Process transformations + // Process transformations — collect source fields used for potential removal in replace mode + let mut used_source_fields: Vec<&str> = Vec::new(); for (target_path, source_field) in &config.transformations { if let Some(value) = result.get(source_field).cloned() { + used_source_fields.push(source_field.as_str()); let normalized_path = normalize_path(target_path); if let Err(e) = set_nested_value(&mut result, &normalized_path, value) { tracing::warn!( @@ -571,6 +768,16 @@ pub(crate) fn process_event_with_config( } } + // In replace mode, remove original source fields after all transformations complete. + // This is done after the loop so fan-out works (multiple targets reading the same source). + if matches!(config.transformation_mode, TransformationMode::Replace) { + if let Some(obj) = result.as_object_mut() { + for source_field in &used_source_fields { + obj.remove(*source_field); + } + } + } + // Process static values - log warnings but continue processing for (target_path, static_value) in &config.static_values { let normalized_path = normalize_path(target_path); diff --git a/crates/common/external-services/Cargo.toml b/crates/common/external-services/Cargo.toml index a0f20b626b..5a2ed4b707 100644 --- a/crates/common/external-services/Cargo.toml +++ b/crates/common/external-services/Cargo.toml @@ -59,6 +59,10 @@ injector = { git = "https://github.com/juspay/hyperswitch", tag = "2026.06.18.0" connector_request_kafka = { path = "../connector_request_kafka", optional = true } url = "2.5.0" chrono = "0.4.31" +log_utils = { git = "https://github.com/juspay/framework-libs-rs", rev = "5d0b5824b2a82dafae492f9930c95060507d91b4", package = "log_utils", features = [ + "tracing", + "tracing-storage-api", +] } # OpenTelemetry (OTLP metrics push) — optional, enabled via the `otel` feature. opentelemetry = { version = "0.27", optional = true, default-features = false, features = [ @@ -76,6 +80,7 @@ injector-client = ["dep:injector"] connector-request-kafka = ["dep:connector_request_kafka"] # Push gRPC metrics over OTLP (in addition to the Prometheus /metrics endpoint). otel = ["dep:opentelemetry"] +log-transformations = ["common_utils/log-transformations"] [lints] workspace = true diff --git a/crates/common/external-services/src/service.rs b/crates/common/external-services/src/service.rs index 7c41ed561c..f973f6022a 100644 --- a/crates/common/external-services/src/service.rs +++ b/crates/common/external-services/src/service.rs @@ -8,6 +8,8 @@ use common_utils::{ events::{EventStage, MaskedSerdeValue}, request::TransportType, }; +#[cfg(all(feature = "injector-client", feature = "log-transformations"))] +use common_utils::events::{apply_log_transformations_to_span, LogTransformationConfig}; use common_utils::{ ext_traits::AsyncExt, lineage, @@ -551,6 +553,12 @@ pub struct EventProcessingParams<'a> { pub merchant_id: &'a str, pub return_raw_connector_data: bool, pub connector_latency: ConnectorLatencyTracker, + /// Pre-compiled log transformations for golden log lines. + #[cfg(feature = "log-transformations")] + pub log_transformations: &'a LogTransformationConfig, + /// Static key-value pairs added to golden log line spans. + /// Patchable via `x-config-override` for per-request overrides. + pub log_static_values: &'a HashMap, } #[cfg(feature = "injector-client")] @@ -1074,10 +1082,47 @@ where let elapsed = start.elapsed().as_millis(); tracing::Span::current().record("latency", elapsed); + // Apply outgoing log transformations before emitting the golden log line + #[cfg(feature = "log-transformations")] + apply_log_transformations_to_span(&event_params.log_transformations.outgoing); + // Apply static key-value pairs (config-driven, overridable via x-config-override) + apply_outgoing_log_static_values(event_params.log_static_values); tracing::info!(tag = ?Tag::OutgoingApi, log_type = "api", "Outgoing Request completed"); result_with_integrity_check } +/// Write static key-value pairs into the current span's storage for outgoing golden log lines. +#[cfg(feature = "injector-client")] +fn apply_outgoing_log_static_values(static_values: &HashMap) { + if static_values.is_empty() { + return; + } + // Filter out reserved keys BEFORE entering the span lock. + // `record_value` calls `tracing::warn!` for reserved keys, which re-enters + // the subscriber and deadlocks when called inside `with_current_span_mut`. + let filtered: Vec<_> = static_values + .iter() + .filter(|(key, _)| { + if log_utils::Storage::is_reserved(key) { + tracing::warn!( + "Static value key `{key}` is reserved by the logging infrastructure, skipping" + ); + false + } else { + true + } + }) + .collect(); + if filtered.is_empty() { + return; + } + log_utils::Storage::with_current_span_mut(|storage| { + for (key, value) in &filtered { + storage.record_value(key, serde_json::Value::String((*value).clone())); + } + }); +} + #[cfg(feature = "injector-client")] fn mask_connector_request(request_content: &Option) -> serde_json::Value { match request_content { diff --git a/crates/common/tracing-kafka/Cargo.toml b/crates/common/tracing-kafka/Cargo.toml index 89a9fc7318..1242ee77bb 100644 --- a/crates/common/tracing-kafka/Cargo.toml +++ b/crates/common/tracing-kafka/Cargo.toml @@ -11,7 +11,7 @@ rdkafka = "0.36" serde_json = { workspace = true } tokio = "1.0" thiserror = { workspace = true } -log_utils = { git = "https://github.com/juspay/framework-libs-rs", rev = "243562041252fe5897ce888d20b715ffdc3767ce", package = "log_utils", features = ["tracing"] } +log_utils = { git = "https://github.com/juspay/framework-libs-rs", rev = "5d0b5824b2a82dafae492f9930c95060507d91b4", package = "log_utils", features = ["tracing"] } # Optional dependencies for metrics prometheus = { version = "0.13", optional = true } diff --git a/crates/common/ucs_env/Cargo.toml b/crates/common/ucs_env/Cargo.toml index 56202101e9..d714b70f1a 100644 --- a/crates/common/ucs_env/Cargo.toml +++ b/crates/common/ucs_env/Cargo.toml @@ -49,16 +49,16 @@ tokio = { version = "1.48.0", features = [ tonic = { workspace = true } tonic-reflection = "0.14.0" http = "1.2.0" -log_utils = { git = "https://github.com/juspay/framework-libs-rs", rev = "243562041252fe5897ce888d20b715ffdc3767ce", package = "log_utils", features = [ +log_utils = { git = "https://github.com/juspay/framework-libs-rs", rev = "5d0b5824b2a82dafae492f9930c95060507d91b4", package = "log_utils", features = [ "tracing", ] } -build_info = { git = "https://github.com/juspay/framework-libs-rs", rev = "243562041252fe5897ce888d20b715ffdc3767ce", package = "build_info", features = [ +build_info = { git = "https://github.com/juspay/framework-libs-rs", rev = "5d0b5824b2a82dafae492f9930c95060507d91b4", package = "build_info", features = [ "cargo-workspace", "framework-libs-members-env", ] } [build-dependencies] -build_info = { git = "https://github.com/juspay/framework-libs-rs", rev = "243562041252fe5897ce888d20b715ffdc3767ce", package = "build_info", features = [ +build_info = { git = "https://github.com/juspay/framework-libs-rs", rev = "5d0b5824b2a82dafae492f9930c95060507d91b4", package = "build_info", features = [ "cargo-workspace-build", "vergen-gix-build", "framework-libs-members-env", @@ -68,6 +68,7 @@ build_info = { git = "https://github.com/juspay/framework-libs-rs", rev = "24356 default = [] kafka = ["tracing-kafka"] otel = ["dep:opentelemetry", "dep:opentelemetry-otlp", "dep:opentelemetry_sdk"] +log-transformations = ["common_utils/log-transformations"] [lints] workspace = true \ No newline at end of file diff --git a/crates/common/ucs_env/src/configs.rs b/crates/common/ucs_env/src/configs.rs index 7d9afce9ab..c995d9154d 100644 --- a/crates/common/ucs_env/src/configs.rs +++ b/crates/common/ucs_env/src/configs.rs @@ -6,10 +6,15 @@ use std::sync::Arc; use common_utils::{ connector_request_kafka::{ConnectorRequestKafkaConfig, ConnectorRequestKafkaConfigPatch}, consts, - events::{EventConfig, EventConfigPatch, RuntimeMetadata, RuntimeMetadataPatch}, + events::{ + EventConfig, EventConfigPatch, RuntimeMetadata, + RuntimeMetadataPatch, + }, metadata::{HeaderMaskingConfig, HeaderMaskingConfigPatch}, SuperpositionConfig, }; +#[cfg(feature = "log-transformations")] +use common_utils::events::LogTransformationConfig; use domain_types::{ connector_types::ConnectorEnum, types::{Connectors, ConnectorsPatch, ProxyConfig, ProxyConfigPatch}, @@ -53,6 +58,12 @@ pub struct Config { #[serde(skip)] #[patch(ignore)] pub superposition_config: Option>, + /// Pre-compiled log transformations for golden log lines. + /// Compiled once at startup from `[log.transformations.incoming]` and `[log.transformations.outgoing]`. + #[cfg(feature = "log-transformations")] + #[serde(skip)] + #[patch(ignore)] + pub log_transformations: Arc, } #[derive(Clone, Deserialize, Debug, Default, Serialize, PartialEq, config_patch_derive::Patch)] @@ -363,6 +374,19 @@ impl Config { error.into_inner() })?; + // Compile log transformations from [log.transformations.incoming] and [log.transformations.outgoing]. + // Pre-splits dotted paths so no string splitting happens at runtime. + #[cfg(feature = "log-transformations")] + let config = { + let mut config = config; + config.log_transformations = Arc::new(LogTransformationConfig::compile( + &config.log.transformations.incoming, + &config.log.transformations.outgoing, + config.events.transformation_mode.clone(), + )); + config + }; + // Validate the environment field config.common.validate()?; diff --git a/crates/common/ucs_env/src/logger/config.rs b/crates/common/ucs_env/src/logger/config.rs index 6ec5d92e58..9e7c9d3c26 100644 --- a/crates/common/ucs_env/src/logger/config.rs +++ b/crates/common/ucs_env/src/logger/config.rs @@ -2,7 +2,22 @@ //! Logger-specific config. //! +use std::collections::HashMap; + use serde::{Deserialize, Serialize}; + +/// Log field transformations for golden log lines (incoming + outgoing). +/// Format: `target_path = "source_field"` — same as `[events.transformations]`. +/// Dotted target paths create nested JSON objects. +#[cfg(feature = "log-transformations")] +#[derive(Debug, Deserialize, Clone, Serialize, PartialEq, Default)] +pub struct LogTransformations { + #[serde(default)] + pub incoming: HashMap, + #[serde(default)] + pub outgoing: HashMap, +} + /// Log config settings. #[derive(Debug, Deserialize, Clone, Serialize, PartialEq, config_patch_derive::Patch)] pub struct Log { @@ -11,6 +26,16 @@ pub struct Log { /// Logging to Kafka (optional). #[serde(default)] pub kafka: Option, + /// Field transformations applied to the two golden log lines. + #[cfg(feature = "log-transformations")] + #[serde(default)] + #[patch(ignore)] + pub transformations: LogTransformations, + /// Static key-value pairs added to golden log line spans. + /// These are flat fields written directly into span storage. + /// Patchable via `x-config-override` header for per-request overrides. + #[serde(default)] + pub static_values: HashMap, } /// Logging to a console. diff --git a/crates/common/ucs_env/src/logger/setup.rs b/crates/common/ucs_env/src/logger/setup.rs index 5463faa461..f96b009f83 100644 --- a/crates/common/ucs_env/src/logger/setup.rs +++ b/crates/common/ucs_env/src/logger/setup.rs @@ -49,7 +49,7 @@ pub fn setup( // Disable color or emphasis related ANSI escape codes for JSON formats error_stack::Report::set_color_mode(error_stack::fmt::ColorMode::None); - log_utils::ConsoleLogFormat::CompactJson + log_utils::ConsoleLogFormat::PrettyJson } }; diff --git a/crates/grpc-server/grpc-server/Cargo.toml b/crates/grpc-server/grpc-server/Cargo.toml index 938418cb2d..43887e058f 100644 --- a/crates/grpc-server/grpc-server/Cargo.toml +++ b/crates/grpc-server/grpc-server/Cargo.toml @@ -66,10 +66,11 @@ prost-types = "0.14" rustc-hash = "2.0" gethostname = "0.5.0" once_cell = "1.19.0" -log_utils = { git = "https://github.com/juspay/framework-libs-rs", rev = "243562041252fe5897ce888d20b715ffdc3767ce", package = "log_utils", features = [ +log_utils = { git = "https://github.com/juspay/framework-libs-rs", rev = "5d0b5824b2a82dafae492f9930c95060507d91b4", package = "log_utils", features = [ "tracing", + "tracing-storage-api", ] } -build_info = { git = "https://github.com/juspay/framework-libs-rs", rev = "243562041252fe5897ce888d20b715ffdc3767ce", package = "build_info", features = [ +build_info = { git = "https://github.com/juspay/framework-libs-rs", rev = "5d0b5824b2a82dafae492f9930c95060507d91b4", package = "build_info", features = [ "cargo-workspace", "framework-libs-members-env", ] } @@ -77,7 +78,7 @@ uuid = { workspace = true } chrono = "0.4.31" [build-dependencies] -build_info = { git = "https://github.com/juspay/framework-libs-rs", rev = "243562041252fe5897ce888d20b715ffdc3767ce", package = "build_info", features = [ +build_info = { git = "https://github.com/juspay/framework-libs-rs", rev = "5d0b5824b2a82dafae492f9930c95060507d91b4", package = "build_info", features = [ "cargo-workspace-build", "vergen-gix-build", "framework-libs-members-env", @@ -101,6 +102,7 @@ connector-request-kafka = ["dep:connector_request_kafka", "external-services/con # Push gRPC metrics over OTLP to an OpenTelemetry Collector, in addition to the # Prometheus /metrics scrape endpoint. otel = ["ucs_env/otel", "external-services/otel"] +log-transformations = ["common_utils/log-transformations", "external-services/log-transformations", "ucs_env/log-transformations"] [lints] workspace = true diff --git a/crates/grpc-server/grpc-server/src/server/disputes.rs b/crates/grpc-server/grpc-server/src/server/disputes.rs index 5c2eb10b94..80ddced743 100644 --- a/crates/grpc-server/grpc-server/src/server/disputes.rs +++ b/crates/grpc-server/grpc-server/src/server/disputes.rs @@ -185,6 +185,9 @@ impl DisputeService for Disputes { merchant_id: merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency, + #[cfg(feature = "log-transformations")] + log_transformations: &config.log_transformations, + log_static_values: &config.log.static_values, }; let response = Box::pin( @@ -425,6 +428,9 @@ impl DisputeService for Disputes { merchant_id: merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency, + #[cfg(feature = "log-transformations")] + log_transformations: &config.log_transformations, + log_static_values: &config.log.static_values, }; let response = Box::pin( diff --git a/crates/grpc-server/grpc-server/src/server/events.rs b/crates/grpc-server/grpc-server/src/server/events.rs index 8299b1aa34..db4e3ca083 100644 --- a/crates/grpc-server/grpc-server/src/server/events.rs +++ b/crates/grpc-server/grpc-server/src/server/events.rs @@ -505,6 +505,9 @@ impl EventServiceImpl { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), + #[cfg(feature = "log-transformations")] + log_transformations: &config.log_transformations, + log_static_values: &config.log.static_values, }; let response_result = Box::pin( @@ -614,6 +617,9 @@ impl EventServiceImpl { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), + #[cfg(feature = "log-transformations")] + log_transformations: &config.log_transformations, + log_static_values: &config.log.static_values, }; let response_result = Box::pin( @@ -723,6 +729,9 @@ impl EventServiceImpl { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), + #[cfg(feature = "log-transformations")] + log_transformations: &config.log_transformations, + log_static_values: &config.log.static_values, }; let response_result = Box::pin( @@ -829,6 +838,9 @@ impl EventServiceImpl { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), + #[cfg(feature = "log-transformations")] + log_transformations: &config.log_transformations, + log_static_values: &config.log.static_values, }; let response_result = Box::pin( @@ -935,6 +947,9 @@ impl EventServiceImpl { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), + #[cfg(feature = "log-transformations")] + log_transformations: &config.log_transformations, + log_static_values: &config.log.static_values, }; let response_result = Box::pin( @@ -1030,6 +1045,9 @@ async fn verify_webhook_source_external( merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), + #[cfg(feature = "log-transformations")] + log_transformations: &config.log_transformations, + log_static_values: &config.log.static_values, }; match Box::pin( diff --git a/crates/grpc-server/grpc-server/src/server/payments.rs b/crates/grpc-server/grpc-server/src/server/payments.rs index 1ccdd45768..996ac42773 100644 --- a/crates/grpc-server/grpc-server/src/server/payments.rs +++ b/crates/grpc-server/grpc-server/src/server/payments.rs @@ -578,6 +578,9 @@ impl Payments { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), + #[cfg(feature = "log-transformations")] + log_transformations: &config.log_transformations, + log_static_values: &config.log.static_values, }; // Execute connector processing - ONLY the authorize call @@ -716,6 +719,9 @@ impl Payments { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), + #[cfg(feature = "log-transformations")] + log_transformations: &config.log_transformations, + log_static_values: &config.log.static_values, }; let response = Box::pin( @@ -1130,6 +1136,9 @@ impl PaymentService for Payments { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), + #[cfg(feature = "log-transformations")] + log_transformations: &config.log_transformations, + log_static_values: &config.log.static_values, }; // handle_response field removed from proto (field 5 reserved) @@ -2608,6 +2617,9 @@ impl PaymentMethod { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), + #[cfg(feature = "log-transformations")] + log_transformations: &config.log_transformations, + log_static_values: &config.log.static_values, }; let response = Box::pin( @@ -2745,6 +2757,9 @@ impl PaymentMethod { return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), runtime_metadata: &config.runtime_metadata, + #[cfg(feature = "log-transformations")] + log_transformations: &config.log_transformations, + log_static_values: &config.log.static_values, }; let response = Box::pin( @@ -2861,6 +2876,9 @@ impl MerchantAuthentication { merchant_id: event_params.merchant_id, return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: event_params.connector_latency.clone(), + #[cfg(feature = "log-transformations")] + log_transformations: &config.log_transformations, + log_static_values: &config.log.static_values, }; // Execute connector processing @@ -3002,6 +3020,9 @@ impl MerchantAuthentication { merchant_id: event_params.merchant_id, return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: event_params.connector_latency.clone(), + #[cfg(feature = "log-transformations")] + log_transformations: &config.log_transformations, + log_static_values: &config.log.static_values, }; let response = Box::pin( @@ -3581,6 +3602,9 @@ impl RecurringPaymentService for RecurringPayments { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), + #[cfg(feature = "log-transformations")] + log_transformations: &config.log_transformations, + log_static_values: &config.log.static_values, }; let response = Box::pin( diff --git a/crates/grpc-server/grpc-server/src/utils.rs b/crates/grpc-server/grpc-server/src/utils.rs index 72b103f699..988989f961 100644 --- a/crates/grpc-server/grpc-server/src/utils.rs +++ b/crates/grpc-server/grpc-server/src/utils.rs @@ -7,11 +7,16 @@ pub use ucs_interface_common::metadata::*; use common_utils::{ consts::{self, Env}, errors::CustomResult, - events::{Event, EventStage, FlowName, MaskedSerdeValue}, + events::{ + Event, EventStage, FlowName, + MaskedSerdeValue, + }, lineage::LineageIds, superposition_config::{get_connector_urls, ConnectorUrls, SuperpositionConfig}, types::ExecutionMode, }; +#[cfg(feature = "log-transformations")] +use common_utils::events::{apply_log_transformations_to_span, LogTransformationConfig}; use domain_types::{ connector_types, errors::IntegrationError, router_data::ConnectorSpecificConfig, }; @@ -289,8 +294,91 @@ where Ok(()) } -pub fn log_after_initialization(result: &Result, tonic::Status>) -where +/// Write static key-value pairs into the current span's storage. +/// Keys are owned Strings — no `Box::leak` needed since Storage accepts `&str`. +fn apply_log_static_values(static_values: &HashMap) { + if static_values.is_empty() { + return; + } + // Filter out reserved keys BEFORE entering the span lock. + // `record_value` calls `tracing::warn!` for reserved keys, which re-enters + // the subscriber and deadlocks when called inside `with_current_span_mut`. + let filtered: Vec<_> = static_values + .iter() + .filter(|(key, _)| { + if log_utils::Storage::is_reserved(key) { + tracing::warn!( + "Static value key `{key}` is reserved by the logging infrastructure, skipping" + ); + false + } else { + true + } + }) + .collect(); + if filtered.is_empty() { + return; + } + log_utils::Storage::with_current_span_mut(|storage| { + for (key, value) in &filtered { + storage.record_value(key, Value::String((*value).clone())); + } + }); +} + +#[cfg(feature = "log-transformations")] +pub fn log_after_initialization( + result: &Result, tonic::Status>, + log_transformations: &LogTransformationConfig, + log_static_values: &HashMap, +) where + T: serde::Serialize + std::fmt::Debug, +{ + let current_span = tracing::Span::current(); + + match &result { + Ok(response) => { + current_span.record("response_body", tracing::field::debug(response.get_ref())); + + let res_ref = response.get_ref(); + + // Try converting to JSON Value + if let Ok(Value::Object(map)) = serde_json::to_value(res_ref) { + if let Some(status_val) = map.get("status") { + let status_num_opt = status_val.as_number(); + let status_u32_opt: Option = status_num_opt + .and_then(|n| n.as_u64()) + .and_then(|n| u32::try_from(n).ok()); + let status_str = if let Some(s) = status_u32_opt { + common_enums::AttemptStatus::try_from(s) + .unwrap_or(common_enums::AttemptStatus::Unknown) + .to_string() + } else { + common_enums::AttemptStatus::Unknown.to_string() + }; + current_span.record("flow_specific_fields.status", status_str); + } + } else { + tracing::warn!("Could not serialize response to JSON to extract status"); + } + } + Err(status) => { + current_span.record("error_message", status.message()); + current_span.record("status_code", status.code().to_string()); + } + } + // Apply incoming log transformations before emitting the golden log line + apply_log_transformations_to_span(&log_transformations.incoming); + // Apply static key-value pairs (config-driven, overridable via x-config-override) + apply_log_static_values(log_static_values); + tracing::info!("Golden Log Line (incoming - response)"); +} + +#[cfg(not(feature = "log-transformations"))] +pub fn log_after_initialization( + result: &Result, tonic::Status>, + log_static_values: &HashMap, +) where T: serde::Serialize + std::fmt::Debug, { let current_span = tracing::Span::current(); @@ -326,6 +414,8 @@ where current_span.record("status_code", status.code().to_string()); } } + // Apply static key-value pairs (config-driven, overridable via x-config-override) + apply_log_static_values(log_static_values); tracing::info!("Golden Log Line (incoming - response)"); } @@ -376,7 +466,10 @@ where .await; let grpc_response = handler_result.into_grpc_status(); - log_after_initialization(&grpc_response); + #[cfg(feature = "log-transformations")] + log_after_initialization(&grpc_response, &config.log_transformations, &config.log.static_values); + #[cfg(not(feature = "log-transformations"))] + log_after_initialization(&grpc_response, &config.log.static_values); #[cfg(feature = "otel")] observe_internal_latency( @@ -440,7 +533,10 @@ where .await; let grpc_response = handler_result.into_grpc_status(); - log_after_initialization(&grpc_response); + #[cfg(feature = "log-transformations")] + log_after_initialization(&grpc_response, &config.log_transformations, &config.log.static_values); + #[cfg(not(feature = "log-transformations"))] + log_after_initialization(&grpc_response, &config.log.static_values); #[cfg(feature = "otel")] observe_internal_latency( @@ -710,6 +806,9 @@ macro_rules! implement_connector_operation { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), + #[cfg(feature = "log-transformations")] + log_transformations: &config.log_transformations, + log_static_values: &config.log.static_values, }; // The connector round-trip is identical for both holders → written once, @@ -1071,6 +1170,9 @@ macro_rules! implement_connector_operation { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), + #[cfg(feature = "log-transformations")] + log_transformations: &config.log_transformations, + log_static_values: &config.log.static_values, }; let call_connector_action = connector_integration.get_call_connector_action(); let response_result = external_services::service::execute_connector_processing_step( From 9a4578641975e2db050196847c6cda7258a75549 Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 05:03:05 +0000 Subject: [PATCH 02/12] chore: auto-fix formatting and generated code Auto-applied by CI: - cargo +nightly fmt --all - make -C sdk generate (if applicable) - make docs (if applicable) This commit was automatically generated by GitHub Actions. --- crates/common/common_utils/src/events.rs | 9 +++----- .../common/external-services/src/service.rs | 4 ++-- crates/common/ucs_env/src/configs.rs | 9 +++----- crates/grpc-server/grpc-server/src/utils.rs | 21 ++++++++++++------- 4 files changed, 21 insertions(+), 22 deletions(-) diff --git a/crates/common/common_utils/src/events.rs b/crates/common/common_utils/src/events.rs index cfdf456504..a10fbe200b 100644 --- a/crates/common/common_utils/src/events.rs +++ b/crates/common/common_utils/src/events.rs @@ -515,12 +515,9 @@ impl CompiledLogTransformations { let rules = raw .iter() .map(|(target_path, source_field)| { - let segments: Vec = - target_path.split('.').map(String::from).collect(); - let root_key: &'static str = - Box::leak(segments[0].clone().into_boxed_str()); - let source_key: &'static str = - Box::leak(source_field.clone().into_boxed_str()); + let segments: Vec = target_path.split('.').map(String::from).collect(); + let root_key: &'static str = Box::leak(segments[0].clone().into_boxed_str()); + let source_key: &'static str = Box::leak(source_field.clone().into_boxed_str()); CompiledLogTransformation { source_field: source_key, target_segments: segments, diff --git a/crates/common/external-services/src/service.rs b/crates/common/external-services/src/service.rs index f973f6022a..ba84e176ad 100644 --- a/crates/common/external-services/src/service.rs +++ b/crates/common/external-services/src/service.rs @@ -2,14 +2,14 @@ use std::{collections::HashMap, str::FromStr, sync::RwLock, time::Duration}; use base64::Engine; use common_enums::ApiClientError; +#[cfg(all(feature = "injector-client", feature = "log-transformations"))] +use common_utils::events::{apply_log_transformations_to_span, LogTransformationConfig}; #[cfg(feature = "injector-client")] use common_utils::{ consts::{X_API_TAG, X_API_URL, X_SESSION_ID}, events::{EventStage, MaskedSerdeValue}, request::TransportType, }; -#[cfg(all(feature = "injector-client", feature = "log-transformations"))] -use common_utils::events::{apply_log_transformations_to_span, LogTransformationConfig}; use common_utils::{ ext_traits::AsyncExt, lineage, diff --git a/crates/common/ucs_env/src/configs.rs b/crates/common/ucs_env/src/configs.rs index c995d9154d..653ba80be5 100644 --- a/crates/common/ucs_env/src/configs.rs +++ b/crates/common/ucs_env/src/configs.rs @@ -3,18 +3,15 @@ use std::path::PathBuf; use std::str::FromStr; use std::sync::Arc; +#[cfg(feature = "log-transformations")] +use common_utils::events::LogTransformationConfig; use common_utils::{ connector_request_kafka::{ConnectorRequestKafkaConfig, ConnectorRequestKafkaConfigPatch}, consts, - events::{ - EventConfig, EventConfigPatch, RuntimeMetadata, - RuntimeMetadataPatch, - }, + events::{EventConfig, EventConfigPatch, RuntimeMetadata, RuntimeMetadataPatch}, metadata::{HeaderMaskingConfig, HeaderMaskingConfigPatch}, SuperpositionConfig, }; -#[cfg(feature = "log-transformations")] -use common_utils::events::LogTransformationConfig; use domain_types::{ connector_types::ConnectorEnum, types::{Connectors, ConnectorsPatch, ProxyConfig, ProxyConfigPatch}, diff --git a/crates/grpc-server/grpc-server/src/utils.rs b/crates/grpc-server/grpc-server/src/utils.rs index 988989f961..67092168bb 100644 --- a/crates/grpc-server/grpc-server/src/utils.rs +++ b/crates/grpc-server/grpc-server/src/utils.rs @@ -4,19 +4,16 @@ pub use ucs_interface_common::config::*; pub use ucs_interface_common::flow::*; pub use ucs_interface_common::metadata::*; +#[cfg(feature = "log-transformations")] +use common_utils::events::{apply_log_transformations_to_span, LogTransformationConfig}; use common_utils::{ consts::{self, Env}, errors::CustomResult, - events::{ - Event, EventStage, FlowName, - MaskedSerdeValue, - }, + events::{Event, EventStage, FlowName, MaskedSerdeValue}, lineage::LineageIds, superposition_config::{get_connector_urls, ConnectorUrls, SuperpositionConfig}, types::ExecutionMode, }; -#[cfg(feature = "log-transformations")] -use common_utils::events::{apply_log_transformations_to_span, LogTransformationConfig}; use domain_types::{ connector_types, errors::IntegrationError, router_data::ConnectorSpecificConfig, }; @@ -467,7 +464,11 @@ where let grpc_response = handler_result.into_grpc_status(); #[cfg(feature = "log-transformations")] - log_after_initialization(&grpc_response, &config.log_transformations, &config.log.static_values); + log_after_initialization( + &grpc_response, + &config.log_transformations, + &config.log.static_values, + ); #[cfg(not(feature = "log-transformations"))] log_after_initialization(&grpc_response, &config.log.static_values); @@ -534,7 +535,11 @@ where let grpc_response = handler_result.into_grpc_status(); #[cfg(feature = "log-transformations")] - log_after_initialization(&grpc_response, &config.log_transformations, &config.log.static_values); + log_after_initialization( + &grpc_response, + &config.log_transformations, + &config.log.static_values, + ); #[cfg(not(feature = "log-transformations"))] log_after_initialization(&grpc_response, &config.log.static_values); From 0d7f308e65376e747992a25f1c51211969652204 Mon Sep 17 00:00:00 2001 From: Amitsingh Tanwar Date: Fri, 7 Aug 2026 10:53:09 +0530 Subject: [PATCH 03/12] fix(events): replace indexing with safe .get()/.first() for clippy::indexing_slicing --- crates/common/common_utils/src/events.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/crates/common/common_utils/src/events.rs b/crates/common/common_utils/src/events.rs index a10fbe200b..1b897c9328 100644 --- a/crates/common/common_utils/src/events.rs +++ b/crates/common/common_utils/src/events.rs @@ -514,15 +514,16 @@ impl CompiledLogTransformations { pub fn compile(raw: &HashMap) -> Self { let rules = raw .iter() - .map(|(target_path, source_field)| { + .filter_map(|(target_path, source_field)| { let segments: Vec = target_path.split('.').map(String::from).collect(); - let root_key: &'static str = Box::leak(segments[0].clone().into_boxed_str()); + let first = segments.first()?; + let root_key: &'static str = Box::leak(first.clone().into_boxed_str()); let source_key: &'static str = Box::leak(source_field.clone().into_boxed_str()); - CompiledLogTransformation { + Some(CompiledLogTransformation { source_field: source_key, target_segments: segments, target_root_key: root_key, - } + }) }) .collect(); Self { rules } @@ -585,8 +586,10 @@ pub fn compute_log_transformations( let root = writes .entry(rule.target_root_key) .or_insert_with(|| serde_json::json!({})); - // Walk pre-compiled segments[1..] to set the value - set_nested_value_from_segments(root, &rule.target_segments[1..], source_value); + // Walk pre-compiled segments past the root to set the value + if let Some(rest) = rule.target_segments.get(1..) { + set_nested_value_from_segments(root, rest, source_value); + } } } @@ -604,8 +607,8 @@ fn set_nested_value_from_segments( if segments.is_empty() { return; } - if segments.len() == 1 { - target[&segments[0]] = value; + if let (1, Some(seg)) = (segments.len(), segments.first()) { + target[seg.as_str()] = value; return; } // Walk intermediate segments, creating objects as needed From 4949a10dfcd361b99e74cce38ebe34f092bda0d0 Mon Sep 17 00:00:00 2001 From: Amitsingh Tanwar Date: Fri, 7 Aug 2026 13:28:08 +0530 Subject: [PATCH 04/12] feat(logs): split log.static_values into incoming and outgoing maps Allows different static key-value pairs for incoming (gRPC handler) and outgoing (connector call) golden log lines. Config format changes from flat [log.static_values] to [log.static_values.incoming] and [log.static_values.outgoing]. --- crates/common/ucs_env/src/logger/config.rs | 14 ++++++++++++-- .../grpc-server/src/server/disputes.rs | 4 ++-- .../grpc-server/grpc-server/src/server/events.rs | 12 ++++++------ .../grpc-server/src/server/payments.rs | 16 ++++++++-------- crates/grpc-server/grpc-server/src/utils.rs | 12 ++++++------ 5 files changed, 34 insertions(+), 24 deletions(-) diff --git a/crates/common/ucs_env/src/logger/config.rs b/crates/common/ucs_env/src/logger/config.rs index 9e7c9d3c26..12000315d9 100644 --- a/crates/common/ucs_env/src/logger/config.rs +++ b/crates/common/ucs_env/src/logger/config.rs @@ -32,10 +32,20 @@ pub struct Log { #[patch(ignore)] pub transformations: LogTransformations, /// Static key-value pairs added to golden log line spans. - /// These are flat fields written directly into span storage. + /// Separate maps for incoming (gRPC handler) and outgoing (connector call) golden log lines. /// Patchable via `x-config-override` header for per-request overrides. #[serde(default)] - pub static_values: HashMap, + pub static_values: LogStaticValues, +} + +/// Static key-value pairs for golden log lines, split by direction. +/// Allows different values for the same field on incoming vs outgoing spans. +#[derive(Debug, Deserialize, Clone, Serialize, PartialEq, Default, config_patch_derive::Patch)] +pub struct LogStaticValues { + #[serde(default)] + pub incoming: HashMap, + #[serde(default)] + pub outgoing: HashMap, } /// Logging to a console. diff --git a/crates/grpc-server/grpc-server/src/server/disputes.rs b/crates/grpc-server/grpc-server/src/server/disputes.rs index 80ddced743..bec6595198 100644 --- a/crates/grpc-server/grpc-server/src/server/disputes.rs +++ b/crates/grpc-server/grpc-server/src/server/disputes.rs @@ -187,7 +187,7 @@ impl DisputeService for Disputes { connector_latency, #[cfg(feature = "log-transformations")] log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values, + log_static_values: &config.log.static_values.outgoing, }; let response = Box::pin( @@ -430,7 +430,7 @@ impl DisputeService for Disputes { connector_latency, #[cfg(feature = "log-transformations")] log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values, + log_static_values: &config.log.static_values.outgoing, }; let response = Box::pin( diff --git a/crates/grpc-server/grpc-server/src/server/events.rs b/crates/grpc-server/grpc-server/src/server/events.rs index db4e3ca083..cbddaa1080 100644 --- a/crates/grpc-server/grpc-server/src/server/events.rs +++ b/crates/grpc-server/grpc-server/src/server/events.rs @@ -507,7 +507,7 @@ impl EventServiceImpl { connector_latency: metadata_payload.connector_latency.clone(), #[cfg(feature = "log-transformations")] log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values, + log_static_values: &config.log.static_values.outgoing, }; let response_result = Box::pin( @@ -619,7 +619,7 @@ impl EventServiceImpl { connector_latency: metadata_payload.connector_latency.clone(), #[cfg(feature = "log-transformations")] log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values, + log_static_values: &config.log.static_values.outgoing, }; let response_result = Box::pin( @@ -731,7 +731,7 @@ impl EventServiceImpl { connector_latency: metadata_payload.connector_latency.clone(), #[cfg(feature = "log-transformations")] log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values, + log_static_values: &config.log.static_values.outgoing, }; let response_result = Box::pin( @@ -840,7 +840,7 @@ impl EventServiceImpl { connector_latency: metadata_payload.connector_latency.clone(), #[cfg(feature = "log-transformations")] log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values, + log_static_values: &config.log.static_values.outgoing, }; let response_result = Box::pin( @@ -949,7 +949,7 @@ impl EventServiceImpl { connector_latency: metadata_payload.connector_latency.clone(), #[cfg(feature = "log-transformations")] log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values, + log_static_values: &config.log.static_values.outgoing, }; let response_result = Box::pin( @@ -1047,7 +1047,7 @@ async fn verify_webhook_source_external( connector_latency: metadata_payload.connector_latency.clone(), #[cfg(feature = "log-transformations")] log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values, + log_static_values: &config.log.static_values.outgoing, }; match Box::pin( diff --git a/crates/grpc-server/grpc-server/src/server/payments.rs b/crates/grpc-server/grpc-server/src/server/payments.rs index 996ac42773..cdbb81cc59 100644 --- a/crates/grpc-server/grpc-server/src/server/payments.rs +++ b/crates/grpc-server/grpc-server/src/server/payments.rs @@ -580,7 +580,7 @@ impl Payments { connector_latency: metadata_payload.connector_latency.clone(), #[cfg(feature = "log-transformations")] log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values, + log_static_values: &config.log.static_values.outgoing, }; // Execute connector processing - ONLY the authorize call @@ -721,7 +721,7 @@ impl Payments { connector_latency: metadata_payload.connector_latency.clone(), #[cfg(feature = "log-transformations")] log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values, + log_static_values: &config.log.static_values.outgoing, }; let response = Box::pin( @@ -1138,7 +1138,7 @@ impl PaymentService for Payments { connector_latency: metadata_payload.connector_latency.clone(), #[cfg(feature = "log-transformations")] log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values, + log_static_values: &config.log.static_values.outgoing, }; // handle_response field removed from proto (field 5 reserved) @@ -2619,7 +2619,7 @@ impl PaymentMethod { connector_latency: metadata_payload.connector_latency.clone(), #[cfg(feature = "log-transformations")] log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values, + log_static_values: &config.log.static_values.outgoing, }; let response = Box::pin( @@ -2759,7 +2759,7 @@ impl PaymentMethod { runtime_metadata: &config.runtime_metadata, #[cfg(feature = "log-transformations")] log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values, + log_static_values: &config.log.static_values.outgoing, }; let response = Box::pin( @@ -2878,7 +2878,7 @@ impl MerchantAuthentication { connector_latency: event_params.connector_latency.clone(), #[cfg(feature = "log-transformations")] log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values, + log_static_values: &config.log.static_values.outgoing, }; // Execute connector processing @@ -3022,7 +3022,7 @@ impl MerchantAuthentication { connector_latency: event_params.connector_latency.clone(), #[cfg(feature = "log-transformations")] log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values, + log_static_values: &config.log.static_values.outgoing, }; let response = Box::pin( @@ -3604,7 +3604,7 @@ impl RecurringPaymentService for RecurringPayments { connector_latency: metadata_payload.connector_latency.clone(), #[cfg(feature = "log-transformations")] log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values, + log_static_values: &config.log.static_values.outgoing, }; let response = Box::pin( diff --git a/crates/grpc-server/grpc-server/src/utils.rs b/crates/grpc-server/grpc-server/src/utils.rs index 67092168bb..1be30988e3 100644 --- a/crates/grpc-server/grpc-server/src/utils.rs +++ b/crates/grpc-server/grpc-server/src/utils.rs @@ -467,10 +467,10 @@ where log_after_initialization( &grpc_response, &config.log_transformations, - &config.log.static_values, + &config.log.static_values.incoming, ); #[cfg(not(feature = "log-transformations"))] - log_after_initialization(&grpc_response, &config.log.static_values); + log_after_initialization(&grpc_response, &config.log.static_values.incoming); #[cfg(feature = "otel")] observe_internal_latency( @@ -538,10 +538,10 @@ where log_after_initialization( &grpc_response, &config.log_transformations, - &config.log.static_values, + &config.log.static_values.incoming, ); #[cfg(not(feature = "log-transformations"))] - log_after_initialization(&grpc_response, &config.log.static_values); + log_after_initialization(&grpc_response, &config.log.static_values.incoming); #[cfg(feature = "otel")] observe_internal_latency( @@ -813,7 +813,7 @@ macro_rules! implement_connector_operation { connector_latency: metadata_payload.connector_latency.clone(), #[cfg(feature = "log-transformations")] log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values, + log_static_values: &config.log.static_values.outgoing, }; // The connector round-trip is identical for both holders → written once, @@ -1177,7 +1177,7 @@ macro_rules! implement_connector_operation { connector_latency: metadata_payload.connector_latency.clone(), #[cfg(feature = "log-transformations")] log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values, + log_static_values: &config.log.static_values.outgoing, }; let call_connector_action = connector_integration.get_call_connector_action(); let response_result = external_services::service::execute_connector_processing_step( From 7f56c525f9f64663f1fcea66bc229fec36f4a9d7 Mon Sep 17 00:00:00 2001 From: Amitsingh Tanwar Date: Sun, 9 Aug 2026 20:54:01 +0530 Subject: [PATCH 05/12] feat(logs): unified log fields with centralized post-patch processing and feature gating Replace separate log.transformations and log.static_values with a single log.fields config supporting Source (span lookup) and Value (literal) entries. Dotted target paths build nested JSON with deep-merge semantics. - Add LogFieldEntry enum, CompiledLogFields compilation, and apply_log_fields() - Add post_patch_processing() on Config for recompiling derived fields - Centralize compiled_outgoing: remove 18 duplicate compile calls, use pre-compiled config.log_fields directly from Config - Recompile log_fields in merge_config_with_override() after patch apply - Gate runtime application behind log-transformations feature flag - x-config-override header merges log.fields with extend (not replace) Co-Authored-By: Claude Opus 4.6 --- crates/common/common_utils/Cargo.toml | 4 +- crates/common/common_utils/src/events.rs | 247 +++++++++++------- .../common/external-services/src/service.rs | 52 +--- crates/common/ucs_env/src/configs.rs | 39 +-- crates/common/ucs_env/src/logger/config.rs | 67 +++-- .../grpc-server/src/server/disputes.rs | 8 +- .../grpc-server/src/server/events.rs | 24 +- .../grpc-server/src/server/payments.rs | 40 ++- crates/grpc-server/grpc-server/src/utils.rs | 120 +-------- .../ucs_interface_common/src/config.rs | 1 + 10 files changed, 258 insertions(+), 344 deletions(-) diff --git a/crates/common/common_utils/Cargo.toml b/crates/common/common_utils/Cargo.toml index fa4ea11414..1d218aea63 100644 --- a/crates/common/common_utils/Cargo.toml +++ b/crates/common/common_utils/Cargo.toml @@ -12,7 +12,7 @@ anyhow = "1.0" rdkafka = { version = "0.36", optional = true } tracing-kafka = { path = "../tracing-kafka", optional = true } tracing = { workspace = true } -log_utils = { git = "https://github.com/juspay/framework-libs-rs", rev = "5d0b5824b2a82dafae492f9930c95060507d91b4", package = "log_utils", features = ["tracing", "tracing-storage-api"], optional = true } +log_utils = { git = "https://github.com/juspay/framework-libs-rs", rev = "5d0b5824b2a82dafae492f9930c95060507d91b4", package = "log_utils", features = ["tracing", "tracing-storage-api"] } once_cell = "1.19" chrono = "0.4.31" @@ -53,7 +53,7 @@ default = [] kafka = ["dep:rdkafka", "dep:tracing-kafka"] async_ext = ["dep:async-trait", "dep:futures"] superposition = ["dep:superposition_core", "dep:superposition_types"] -log-transformations = ["dep:log_utils"] +log-transformations = [] [dependencies.async-trait] version = "0.1.74" diff --git a/crates/common/common_utils/src/events.rs b/crates/common/common_utils/src/events.rs index 1b897c9328..d322c90cf2 100644 --- a/crates/common/common_utils/src/events.rs +++ b/crates/common/common_utils/src/events.rs @@ -485,115 +485,204 @@ pub enum TransformationMode { Replace, } -/// A single log transformation with pre-split target path segments. -#[cfg(feature = "log-transformations")] +/// A single field entry for golden log lines. +/// +/// Each entry is either a literal value or a reference to a span storage field. +/// - `{ value = "literal" }` — injects a constant string. +/// - `{ source = "span_field" }` — reads the value from the named span storage field at runtime. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] +pub enum LogFieldEntry { + /// Read value from the named field in span storage. + Source { source: String }, + /// Use a literal string value. + Value { value: String }, +} + +/// A single compiled log field with pre-split target path segments. #[derive(Debug, Clone)] -pub struct CompiledLogTransformation { - /// Source field key in span storage (e.g., "gateway", "request.url"). - /// Leaked to `&'static str` at startup for `Storage::record_value()`. - pub source_field: &'static str, +pub struct CompiledLogField { /// Pre-split target path segments (e.g., `["api_details", "url"]`). /// Segment 0 is the root key; segments 1.. are the nested sub-path. pub target_segments: Vec, - /// Top-level key for `Storage::record_value()` — leaked `&'static str`. - pub target_root_key: &'static str, + /// Top-level key — segment 0 of the target path. + pub target_root_key: String, + /// Resolved entry: either a source field name or a literal value. + pub entry: CompiledFieldEntry, } -/// Pre-compiled transformations for one golden log line type (incoming or outgoing). -#[cfg(feature = "log-transformations")] +/// Resolved field entry — either a source field reference or a literal value. +#[derive(Debug, Clone)] +pub enum CompiledFieldEntry { + /// Read value from this field name in span storage at runtime. + Source(String), + /// Use this literal value. + Value(serde_json::Value), +} + +/// Pre-compiled fields for one golden log line type (incoming or outgoing). #[derive(Debug, Clone, Default)] -pub struct CompiledLogTransformations { - pub rules: Vec, +pub struct CompiledLogFields { + pub rules: Vec, } -#[cfg(feature = "log-transformations")] -impl CompiledLogTransformations { - /// Compile raw `target_path → source_field` mappings into pre-split rules. - /// Uses `Box::leak` to produce `&'static str` keys — safe because config is loaded once - /// at process startup and lives for the entire process lifetime. - pub fn compile(raw: &HashMap) -> Self { +impl CompiledLogFields { + /// Compile raw `target_path → LogFieldEntry` mappings into pre-split rules. + pub fn compile(raw: &HashMap) -> Self { let rules = raw .iter() - .filter_map(|(target_path, source_field)| { + .filter_map(|(target_path, field_entry)| { let segments: Vec = target_path.split('.').map(String::from).collect(); - let first = segments.first()?; - let root_key: &'static str = Box::leak(first.clone().into_boxed_str()); - let source_key: &'static str = Box::leak(source_field.clone().into_boxed_str()); - Some(CompiledLogTransformation { - source_field: source_key, + let root_key = segments.first()?.clone(); + let entry = match field_entry { + LogFieldEntry::Source { source } => CompiledFieldEntry::Source(source.clone()), + LogFieldEntry::Value { value } => { + CompiledFieldEntry::Value(serde_json::Value::String(value.clone())) + } + }; + Some(CompiledLogField { target_segments: segments, target_root_key: root_key, + entry, }) }) .collect(); Self { rules } } - /// Returns `true` if no transformation rules are configured. + /// Returns `true` if no field rules are configured. pub fn is_empty(&self) -> bool { self.rules.is_empty() } } -/// Holds compiled transformations for both golden log lines. -#[cfg(feature = "log-transformations")] +/// Holds compiled fields for both golden log lines. #[derive(Debug, Clone, Default)] -pub struct LogTransformationConfig { - pub incoming: CompiledLogTransformations, - pub outgoing: CompiledLogTransformations, - pub mode: TransformationMode, +pub struct CompiledLogFieldsConfig { + pub incoming: CompiledLogFields, + pub outgoing: CompiledLogFields, } -#[cfg(feature = "log-transformations")] -impl LogTransformationConfig { +impl CompiledLogFieldsConfig { pub fn compile( - incoming: &HashMap, - outgoing: &HashMap, - mode: TransformationMode, + incoming: &HashMap, + outgoing: &HashMap, ) -> Self { Self { - incoming: CompiledLogTransformations::compile(incoming), - outgoing: CompiledLogTransformations::compile(outgoing), - mode, + incoming: CompiledLogFields::compile(incoming), + outgoing: CompiledLogFields::compile(outgoing), } } } -/// Given span storage values and compiled transformation rules, compute the new key-value -/// pairs to write back into span storage. Returns a list of `(root_key, value)` pairs. +/// Apply compiled log field rules to the current span's storage. /// -/// For simple renames (1 segment), the value is copied directly. -/// For nested targets (2+ segments), values are grouped under the root key as a nested JSON object -/// using `set_nested_value_from_segments()`. +/// For each rule: +/// - `Source` entries read a value from the span storage snapshot. +/// - `Value` entries use the literal value directly. +/// +/// Dotted target paths build nested JSON objects. When writing a nested object, +/// it is deep-merged with any existing value for the same root key in the span. #[cfg(feature = "log-transformations")] -pub fn compute_log_transformations( - storage_values: &HashMap, - compiled: &CompiledLogTransformations, -) -> Vec<(&'static str, serde_json::Value)> { - let mut writes: HashMap<&'static str, serde_json::Value> = HashMap::new(); +pub fn apply_log_fields(compiled: &CompiledLogFields) { + if compiled.is_empty() { + return; + } + // Snapshot span storage (needed for Source entries to read current values). + let snapshot: Option> = + log_utils::Storage::with_current_span(|storage| storage.values().clone()); + + // Build writes: for each rule, resolve the value and group by root key. + let mut writes: HashMap = HashMap::new(); for rule in &compiled.rules { - let source_value = match storage_values.get(rule.source_field) { - Some(v) => v.clone(), - None => continue, + let resolved_value = match &rule.entry { + CompiledFieldEntry::Value(v) => v.clone(), + CompiledFieldEntry::Source(field) => { + match snapshot + .as_ref() + .and_then(|s| s.get(field.as_str())) + .cloned() + { + Some(v) => v, + None => continue, // source field not present in span, skip + } + } }; if rule.target_segments.len() == 1 { - // Simple rename: "connector" = "gateway" - writes.insert(rule.target_root_key, source_value); + // Flat key: "connector" = { source = "gateway" } + writes.insert(rule.target_root_key.clone(), resolved_value); } else { - // Nested target: "api_details.url" = "request.url" + // Nested target: "api_details.url" = { source = "request.url" } let root = writes - .entry(rule.target_root_key) + .entry(rule.target_root_key.clone()) .or_insert_with(|| serde_json::json!({})); - // Walk pre-compiled segments past the root to set the value if let Some(rest) = rule.target_segments.get(1..) { - set_nested_value_from_segments(root, rest, source_value); + set_nested_value_from_segments(root, rest, resolved_value); } } } - writes.into_iter().collect() + // Filter out reserved keys BEFORE entering the span lock. + // `record_value` calls `tracing::warn!` for reserved keys, which re-enters + // the subscriber and deadlocks when called inside `with_current_span_mut`. + let writes: Vec<_> = writes + .into_iter() + .filter(|(key, _)| { + if log_utils::Storage::is_reserved(key) { + tracing::warn!( + "Log field target key `{key}` is reserved by the logging infrastructure, skipping" + ); + false + } else { + true + } + }) + .collect(); + + if writes.is_empty() { + return; + } + + // Write to span storage, deep-merging nested objects with existing values. + log_utils::Storage::with_current_span_mut(|storage| { + for (key, value) in writes { + if value.is_object() { + // Deep-merge with existing object if present + if let Some(existing) = storage.values().get(&key).cloned() { + if existing.is_object() { + let mut merged = existing; + deep_merge_json(&mut merged, value); + storage.record_value(&key, merged); + continue; + } + } + } + storage.record_value(&key, value); + } + }); +} + +/// Recursively merge `source` JSON object into `target`. +/// Keys in `source` overwrite matching keys in `target`. +/// Nested objects are merged recursively. +#[cfg(feature = "log-transformations")] +fn deep_merge_json(target: &mut serde_json::Value, source: serde_json::Value) { + if let (serde_json::Value::Object(target_map), serde_json::Value::Object(source_map)) = + (target, source) + { + for (key, value) in source_map { + let entry = target_map + .entry(key) + .or_insert(serde_json::Value::Null); + if entry.is_object() && value.is_object() { + deep_merge_json(entry, value); + } else { + *entry = value; + } + } + } } /// Like `set_nested_value` but takes pre-split path segments instead of a dotted string. @@ -625,46 +714,6 @@ fn set_nested_value_from_segments( } } -/// Apply compiled log transformation rules to the current span's storage. -/// Snapshots the current span storage, computes new fields from the rules, -/// and writes them back. -#[cfg(feature = "log-transformations")] -pub fn apply_log_transformations_to_span(compiled: &CompiledLogTransformations) { - if compiled.is_empty() { - return; - } - let snapshot: Option> = - log_utils::Storage::with_current_span(|storage| storage.values().clone()); - let Some(snapshot) = snapshot else { - return; - }; - let writes = compute_log_transformations(&snapshot, compiled); - // Filter out reserved keys BEFORE entering the span lock. - // `record_value` calls `tracing::warn!` for reserved keys, which re-enters - // the subscriber and deadlocks when called inside `with_current_span_mut`. - let writes: Vec<_> = writes - .into_iter() - .filter(|(key, _)| { - if log_utils::Storage::is_reserved(key) { - tracing::warn!( - "Log transformation target key `{key}` is reserved by the logging infrastructure, skipping" - ); - false - } else { - true - } - }) - .collect(); - if writes.is_empty() { - return; - } - log_utils::Storage::with_current_span_mut(|storage| { - for (key, value) in writes { - storage.record_value(key, value); - } - }); -} - /// Configuration for events system #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, config_patch_derive::Patch)] pub struct EventConfig { diff --git a/crates/common/external-services/src/service.rs b/crates/common/external-services/src/service.rs index ba84e176ad..19e73bdd60 100644 --- a/crates/common/external-services/src/service.rs +++ b/crates/common/external-services/src/service.rs @@ -2,14 +2,14 @@ use std::{collections::HashMap, str::FromStr, sync::RwLock, time::Duration}; use base64::Engine; use common_enums::ApiClientError; -#[cfg(all(feature = "injector-client", feature = "log-transformations"))] -use common_utils::events::{apply_log_transformations_to_span, LogTransformationConfig}; #[cfg(feature = "injector-client")] use common_utils::{ consts::{X_API_TAG, X_API_URL, X_SESSION_ID}, - events::{EventStage, MaskedSerdeValue}, + events::{CompiledLogFields, EventStage, MaskedSerdeValue}, request::TransportType, }; +#[cfg(all(feature = "injector-client", feature = "log-transformations"))] +use common_utils::events::apply_log_fields; use common_utils::{ ext_traits::AsyncExt, lineage, @@ -553,12 +553,8 @@ pub struct EventProcessingParams<'a> { pub merchant_id: &'a str, pub return_raw_connector_data: bool, pub connector_latency: ConnectorLatencyTracker, - /// Pre-compiled log transformations for golden log lines. - #[cfg(feature = "log-transformations")] - pub log_transformations: &'a LogTransformationConfig, - /// Static key-value pairs added to golden log line spans. - /// Patchable via `x-config-override` for per-request overrides. - pub log_static_values: &'a HashMap, + /// Pre-compiled log fields (transformations + static values) for golden log lines. + pub log_fields: &'a CompiledLogFields, } #[cfg(feature = "injector-client")] @@ -1082,47 +1078,13 @@ where let elapsed = start.elapsed().as_millis(); tracing::Span::current().record("latency", elapsed); - // Apply outgoing log transformations before emitting the golden log line + // Apply outgoing log fields (transformations + static values) before emitting the golden log line #[cfg(feature = "log-transformations")] - apply_log_transformations_to_span(&event_params.log_transformations.outgoing); - // Apply static key-value pairs (config-driven, overridable via x-config-override) - apply_outgoing_log_static_values(event_params.log_static_values); + apply_log_fields(event_params.log_fields); tracing::info!(tag = ?Tag::OutgoingApi, log_type = "api", "Outgoing Request completed"); result_with_integrity_check } -/// Write static key-value pairs into the current span's storage for outgoing golden log lines. -#[cfg(feature = "injector-client")] -fn apply_outgoing_log_static_values(static_values: &HashMap) { - if static_values.is_empty() { - return; - } - // Filter out reserved keys BEFORE entering the span lock. - // `record_value` calls `tracing::warn!` for reserved keys, which re-enters - // the subscriber and deadlocks when called inside `with_current_span_mut`. - let filtered: Vec<_> = static_values - .iter() - .filter(|(key, _)| { - if log_utils::Storage::is_reserved(key) { - tracing::warn!( - "Static value key `{key}` is reserved by the logging infrastructure, skipping" - ); - false - } else { - true - } - }) - .collect(); - if filtered.is_empty() { - return; - } - log_utils::Storage::with_current_span_mut(|storage| { - for (key, value) in &filtered { - storage.record_value(key, serde_json::Value::String((*value).clone())); - } - }); -} - #[cfg(feature = "injector-client")] fn mask_connector_request(request_content: &Option) -> serde_json::Value { match request_content { diff --git a/crates/common/ucs_env/src/configs.rs b/crates/common/ucs_env/src/configs.rs index 653ba80be5..426de44cc9 100644 --- a/crates/common/ucs_env/src/configs.rs +++ b/crates/common/ucs_env/src/configs.rs @@ -3,12 +3,13 @@ use std::path::PathBuf; use std::str::FromStr; use std::sync::Arc; -#[cfg(feature = "log-transformations")] -use common_utils::events::LogTransformationConfig; use common_utils::{ connector_request_kafka::{ConnectorRequestKafkaConfig, ConnectorRequestKafkaConfigPatch}, consts, - events::{EventConfig, EventConfigPatch, RuntimeMetadata, RuntimeMetadataPatch}, + events::{ + CompiledLogFieldsConfig, EventConfig, EventConfigPatch, RuntimeMetadata, + RuntimeMetadataPatch, + }, metadata::{HeaderMaskingConfig, HeaderMaskingConfigPatch}, SuperpositionConfig, }; @@ -55,12 +56,12 @@ pub struct Config { #[serde(skip)] #[patch(ignore)] pub superposition_config: Option>, - /// Pre-compiled log transformations for golden log lines. - /// Compiled once at startup from `[log.transformations.incoming]` and `[log.transformations.outgoing]`. - #[cfg(feature = "log-transformations")] + /// Pre-compiled log fields for golden log lines. + /// Compiled at startup from `[log.fields.incoming]` and `[log.fields.outgoing]`. + /// Recompiled per-request when `x-config-override` patches `log.fields`. #[serde(skip)] #[patch(ignore)] - pub log_transformations: Arc, + pub log_fields: Arc, } #[derive(Clone, Deserialize, Debug, Default, Serialize, PartialEq, config_patch_derive::Patch)] @@ -336,6 +337,21 @@ impl WebhookSourceVerificationCall { } impl Config { + /// Recompute derived / cached fields from the raw config values. + /// + /// Call this after deserialization **and** after applying a config-override patch. + /// Any `#[serde(skip)] #[patch(ignore)]` field that is derived from patchable + /// config should be rebuilt here. + pub fn post_patch_processing(&mut self) { + #[cfg(feature = "log-transformations")] + { + self.log_fields = Arc::new(CompiledLogFieldsConfig::compile( + &self.log.fields.incoming, + &self.log.fields.outgoing, + )); + } + } + /// Function to build the configuration by picking it from default locations pub fn new() -> Result { Self::new_with_config_path(None) @@ -371,16 +387,9 @@ impl Config { error.into_inner() })?; - // Compile log transformations from [log.transformations.incoming] and [log.transformations.outgoing]. - // Pre-splits dotted paths so no string splitting happens at runtime. - #[cfg(feature = "log-transformations")] let config = { let mut config = config; - config.log_transformations = Arc::new(LogTransformationConfig::compile( - &config.log.transformations.incoming, - &config.log.transformations.outgoing, - config.events.transformation_mode.clone(), - )); + config.post_patch_processing(); config }; diff --git a/crates/common/ucs_env/src/logger/config.rs b/crates/common/ucs_env/src/logger/config.rs index 12000315d9..47f37222ef 100644 --- a/crates/common/ucs_env/src/logger/config.rs +++ b/crates/common/ucs_env/src/logger/config.rs @@ -4,18 +4,44 @@ use std::collections::HashMap; +use common_utils::config_patch::Patch; +pub use common_utils::events::LogFieldEntry; use serde::{Deserialize, Serialize}; -/// Log field transformations for golden log lines (incoming + outgoing). -/// Format: `target_path = "source_field"` — same as `[events.transformations]`. -/// Dotted target paths create nested JSON objects. -#[cfg(feature = "log-transformations")] -#[derive(Debug, Deserialize, Clone, Serialize, PartialEq, Default)] -pub struct LogTransformations { +/// Unified log fields for golden log lines, split by direction. +/// +/// Each map key is a target path (dotted paths like `"api_details.url"` create nested JSON). +/// Each value is a [`LogFieldEntry`] — either a literal or a source field reference. +/// +/// Patchable via `x-config-override` header with **merge** semantics: +/// override entries are added to / replace matching base entries; unmentioned entries stay. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] +pub struct LogFields { #[serde(default)] - pub incoming: HashMap, + pub incoming: HashMap, #[serde(default)] - pub outgoing: HashMap, + pub outgoing: HashMap, +} + +/// Patch type for [`LogFields`] — manually defined for merge semantics. +/// +/// When applied, each direction's map is **extended** (not replaced): +/// new keys are added, existing keys are overwritten, absent keys are kept. +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct LogFieldsPatch { + pub incoming: Option>, + pub outgoing: Option>, +} + +impl Patch for LogFields { + fn apply(&mut self, patch: LogFieldsPatch) { + if let Some(incoming) = patch.incoming { + self.incoming.extend(incoming); + } + if let Some(outgoing) = patch.outgoing { + self.outgoing.extend(outgoing); + } + } } /// Log config settings. @@ -26,26 +52,13 @@ pub struct Log { /// Logging to Kafka (optional). #[serde(default)] pub kafka: Option, - /// Field transformations applied to the two golden log lines. - #[cfg(feature = "log-transformations")] - #[serde(default)] - #[patch(ignore)] - pub transformations: LogTransformations, - /// Static key-value pairs added to golden log line spans. - /// Separate maps for incoming (gRPC handler) and outgoing (connector call) golden log lines. - /// Patchable via `x-config-override` header for per-request overrides. - #[serde(default)] - pub static_values: LogStaticValues, -} - -/// Static key-value pairs for golden log lines, split by direction. -/// Allows different values for the same field on incoming vs outgoing spans. -#[derive(Debug, Deserialize, Clone, Serialize, PartialEq, Default, config_patch_derive::Patch)] -pub struct LogStaticValues { - #[serde(default)] - pub incoming: HashMap, + /// Unified log fields for golden log lines. + /// Each entry is either a literal value or a source field reference. + /// Dotted target paths create nested JSON objects. + /// Patchable per-request via `x-config-override` with merge semantics. #[serde(default)] - pub outgoing: HashMap, + #[patch(patch_type = LogFieldsPatch)] + pub fields: LogFields, } /// Logging to a console. diff --git a/crates/grpc-server/grpc-server/src/server/disputes.rs b/crates/grpc-server/grpc-server/src/server/disputes.rs index bec6595198..4a59825519 100644 --- a/crates/grpc-server/grpc-server/src/server/disputes.rs +++ b/crates/grpc-server/grpc-server/src/server/disputes.rs @@ -185,9 +185,7 @@ impl DisputeService for Disputes { merchant_id: merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency, - #[cfg(feature = "log-transformations")] - log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values.outgoing, + log_fields: &config.log_fields.outgoing, }; let response = Box::pin( @@ -428,9 +426,7 @@ impl DisputeService for Disputes { merchant_id: merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency, - #[cfg(feature = "log-transformations")] - log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values.outgoing, + log_fields: &config.log_fields.outgoing, }; let response = Box::pin( diff --git a/crates/grpc-server/grpc-server/src/server/events.rs b/crates/grpc-server/grpc-server/src/server/events.rs index cbddaa1080..ad33fc7c7f 100644 --- a/crates/grpc-server/grpc-server/src/server/events.rs +++ b/crates/grpc-server/grpc-server/src/server/events.rs @@ -505,9 +505,7 @@ impl EventServiceImpl { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), - #[cfg(feature = "log-transformations")] - log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values.outgoing, + log_fields: &config.log_fields.outgoing, }; let response_result = Box::pin( @@ -617,9 +615,7 @@ impl EventServiceImpl { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), - #[cfg(feature = "log-transformations")] - log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values.outgoing, + log_fields: &config.log_fields.outgoing, }; let response_result = Box::pin( @@ -729,9 +725,7 @@ impl EventServiceImpl { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), - #[cfg(feature = "log-transformations")] - log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values.outgoing, + log_fields: &config.log_fields.outgoing, }; let response_result = Box::pin( @@ -838,9 +832,7 @@ impl EventServiceImpl { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), - #[cfg(feature = "log-transformations")] - log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values.outgoing, + log_fields: &config.log_fields.outgoing, }; let response_result = Box::pin( @@ -947,9 +939,7 @@ impl EventServiceImpl { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), - #[cfg(feature = "log-transformations")] - log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values.outgoing, + log_fields: &config.log_fields.outgoing, }; let response_result = Box::pin( @@ -1045,9 +1035,7 @@ async fn verify_webhook_source_external( merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), - #[cfg(feature = "log-transformations")] - log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values.outgoing, + log_fields: &config.log_fields.outgoing, }; match Box::pin( diff --git a/crates/grpc-server/grpc-server/src/server/payments.rs b/crates/grpc-server/grpc-server/src/server/payments.rs index cdbb81cc59..c414788068 100644 --- a/crates/grpc-server/grpc-server/src/server/payments.rs +++ b/crates/grpc-server/grpc-server/src/server/payments.rs @@ -561,6 +561,7 @@ impl Payments { })?; // Execute connector processing + let event_params = EventProcessingParams { connector_name: &connector.get_connector_name(), service_name, @@ -578,9 +579,7 @@ impl Payments { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), - #[cfg(feature = "log-transformations")] - log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values.outgoing, + log_fields: &config.log_fields.outgoing, }; // Execute connector processing - ONLY the authorize call @@ -702,6 +701,7 @@ impl Payments { )) })?; + let event_params = EventProcessingParams { connector_name: &connector.get_connector_name(), service_name, @@ -719,9 +719,7 @@ impl Payments { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), - #[cfg(feature = "log-transformations")] - log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values.outgoing, + log_fields: &config.log_fields.outgoing, }; let response = Box::pin( @@ -1119,6 +1117,7 @@ impl PaymentService for Payments { })) })?; + let event_params = EventProcessingParams { connector_name: &connector.get_connector_name(), service_name: &service_name, @@ -1136,9 +1135,7 @@ impl PaymentService for Payments { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), - #[cfg(feature = "log-transformations")] - log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values.outgoing, + log_fields: &config.log_fields.outgoing, }; // handle_response field removed from proto (field 5 reserved) @@ -2600,6 +2597,7 @@ impl PaymentMethod { })?; // Execute connector processing + let event_params = EventProcessingParams { connector_name: &connector.get_connector_name(), service_name, @@ -2617,9 +2615,7 @@ impl PaymentMethod { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), - #[cfg(feature = "log-transformations")] - log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values.outgoing, + log_fields: &config.log_fields.outgoing, }; let response = Box::pin( @@ -2740,6 +2736,7 @@ impl PaymentMethod { )) })?; + let event_params = EventProcessingParams { connector_name: &metadata_payload.connector.get_connector_name(), service_name, @@ -2757,9 +2754,7 @@ impl PaymentMethod { return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), runtime_metadata: &config.runtime_metadata, - #[cfg(feature = "log-transformations")] - log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values.outgoing, + log_fields: &config.log_fields.outgoing, }; let response = Box::pin( @@ -2859,6 +2854,7 @@ impl MerchantAuthentication { })?; // Create event processing parameters + let external_event_params = EventProcessingParams { connector_name, service_name, @@ -2876,9 +2872,7 @@ impl MerchantAuthentication { merchant_id: event_params.merchant_id, return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: event_params.connector_latency.clone(), - #[cfg(feature = "log-transformations")] - log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values.outgoing, + log_fields: &config.log_fields.outgoing, }; // Execute connector processing @@ -3003,6 +2997,7 @@ impl MerchantAuthentication { })?; // Execute connector processing + let external_event_params = EventProcessingParams { connector_name, service_name, @@ -3020,9 +3015,7 @@ impl MerchantAuthentication { merchant_id: event_params.merchant_id, return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: event_params.connector_latency.clone(), - #[cfg(feature = "log-transformations")] - log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values.outgoing, + log_fields: &config.log_fields.outgoing, }; let response = Box::pin( @@ -3585,6 +3578,7 @@ impl RecurringPaymentService for RecurringPayments { })) })?; + let event_params = EventProcessingParams { connector_name: &metadata_payload.connector.get_connector_name(), service_name: &service_name, @@ -3602,9 +3596,7 @@ impl RecurringPaymentService for RecurringPayments { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), - #[cfg(feature = "log-transformations")] - log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values.outgoing, + log_fields: &config.log_fields.outgoing, }; let response = Box::pin( diff --git a/crates/grpc-server/grpc-server/src/utils.rs b/crates/grpc-server/grpc-server/src/utils.rs index 1be30988e3..a3c07b2657 100644 --- a/crates/grpc-server/grpc-server/src/utils.rs +++ b/crates/grpc-server/grpc-server/src/utils.rs @@ -5,11 +5,11 @@ pub use ucs_interface_common::flow::*; pub use ucs_interface_common::metadata::*; #[cfg(feature = "log-transformations")] -use common_utils::events::{apply_log_transformations_to_span, LogTransformationConfig}; +use common_utils::events::apply_log_fields; use common_utils::{ consts::{self, Env}, errors::CustomResult, - events::{Event, EventStage, FlowName, MaskedSerdeValue}, + events::{CompiledLogFields, Event, EventStage, FlowName, MaskedSerdeValue}, lineage::LineageIds, superposition_config::{get_connector_urls, ConnectorUrls, SuperpositionConfig}, types::ExecutionMode, @@ -291,43 +291,9 @@ where Ok(()) } -/// Write static key-value pairs into the current span's storage. -/// Keys are owned Strings — no `Box::leak` needed since Storage accepts `&str`. -fn apply_log_static_values(static_values: &HashMap) { - if static_values.is_empty() { - return; - } - // Filter out reserved keys BEFORE entering the span lock. - // `record_value` calls `tracing::warn!` for reserved keys, which re-enters - // the subscriber and deadlocks when called inside `with_current_span_mut`. - let filtered: Vec<_> = static_values - .iter() - .filter(|(key, _)| { - if log_utils::Storage::is_reserved(key) { - tracing::warn!( - "Static value key `{key}` is reserved by the logging infrastructure, skipping" - ); - false - } else { - true - } - }) - .collect(); - if filtered.is_empty() { - return; - } - log_utils::Storage::with_current_span_mut(|storage| { - for (key, value) in &filtered { - storage.record_value(key, Value::String((*value).clone())); - } - }); -} - -#[cfg(feature = "log-transformations")] pub fn log_after_initialization( result: &Result, tonic::Status>, - log_transformations: &LogTransformationConfig, - log_static_values: &HashMap, + log_fields: &CompiledLogFields, ) where T: serde::Serialize + std::fmt::Debug, { @@ -364,55 +330,11 @@ pub fn log_after_initialization( current_span.record("status_code", status.code().to_string()); } } - // Apply incoming log transformations before emitting the golden log line - apply_log_transformations_to_span(&log_transformations.incoming); - // Apply static key-value pairs (config-driven, overridable via x-config-override) - apply_log_static_values(log_static_values); - tracing::info!("Golden Log Line (incoming - response)"); -} - -#[cfg(not(feature = "log-transformations"))] -pub fn log_after_initialization( - result: &Result, tonic::Status>, - log_static_values: &HashMap, -) where - T: serde::Serialize + std::fmt::Debug, -{ - let current_span = tracing::Span::current(); - - match &result { - Ok(response) => { - current_span.record("response_body", tracing::field::debug(response.get_ref())); - - let res_ref = response.get_ref(); - - // Try converting to JSON Value - if let Ok(Value::Object(map)) = serde_json::to_value(res_ref) { - if let Some(status_val) = map.get("status") { - let status_num_opt = status_val.as_number(); - let status_u32_opt: Option = status_num_opt - .and_then(|n| n.as_u64()) - .and_then(|n| u32::try_from(n).ok()); - let status_str = if let Some(s) = status_u32_opt { - common_enums::AttemptStatus::try_from(s) - .unwrap_or(common_enums::AttemptStatus::Unknown) - .to_string() - } else { - common_enums::AttemptStatus::Unknown.to_string() - }; - current_span.record("flow_specific_fields.status", status_str); - } - } else { - tracing::warn!("Could not serialize response to JSON to extract status"); - } - } - Err(status) => { - current_span.record("error_message", status.message()); - current_span.record("status_code", status.code().to_string()); - } - } - // Apply static key-value pairs (config-driven, overridable via x-config-override) - apply_log_static_values(log_static_values); + // Apply unified log fields (transformations + static values) before emitting the golden log line + #[cfg(feature = "log-transformations")] + apply_log_fields(log_fields); + #[cfg(not(feature = "log-transformations"))] + let _ = log_fields; tracing::info!("Golden Log Line (incoming - response)"); } @@ -463,14 +385,7 @@ where .await; let grpc_response = handler_result.into_grpc_status(); - #[cfg(feature = "log-transformations")] - log_after_initialization( - &grpc_response, - &config.log_transformations, - &config.log.static_values.incoming, - ); - #[cfg(not(feature = "log-transformations"))] - log_after_initialization(&grpc_response, &config.log.static_values.incoming); + log_after_initialization(&grpc_response, &config.log_fields.incoming); #[cfg(feature = "otel")] observe_internal_latency( @@ -534,14 +449,7 @@ where .await; let grpc_response = handler_result.into_grpc_status(); - #[cfg(feature = "log-transformations")] - log_after_initialization( - &grpc_response, - &config.log_transformations, - &config.log.static_values.incoming, - ); - #[cfg(not(feature = "log-transformations"))] - log_after_initialization(&grpc_response, &config.log.static_values.incoming); + log_after_initialization(&grpc_response, &config.log_fields.incoming); #[cfg(feature = "otel")] observe_internal_latency( @@ -811,9 +719,7 @@ macro_rules! implement_connector_operation { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), - #[cfg(feature = "log-transformations")] - log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values.outgoing, + log_fields: &config.log_fields.outgoing, }; // The connector round-trip is identical for both holders → written once, @@ -1175,9 +1081,7 @@ macro_rules! implement_connector_operation { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), - #[cfg(feature = "log-transformations")] - log_transformations: &config.log_transformations, - log_static_values: &config.log.static_values.outgoing, + log_fields: &config.log_fields.outgoing, }; let call_connector_action = connector_integration.get_call_connector_action(); let response_result = external_services::service::execute_connector_processing_step( diff --git a/crates/types-traits/ucs_interface_common/src/config.rs b/crates/types-traits/ucs_interface_common/src/config.rs index a7f0b01e8f..fbf9375bab 100644 --- a/crates/types-traits/ucs_interface_common/src/config.rs +++ b/crates/types-traits/ucs_interface_common/src/config.rs @@ -33,6 +33,7 @@ pub fn merge_config_with_override( let mut merged_config = config; merged_config.apply(override_patch); + merged_config.post_patch_processing(); tracing::info!("Config override applied successfully"); From 5bab766a9d9cc89a15c96f66d2dea19e52f1cb7e Mon Sep 17 00:00:00 2001 From: Amitsingh Tanwar Date: Sun, 9 Aug 2026 21:02:16 +0530 Subject: [PATCH 06/12] fix(logs): restore CompactJson log format (revert testing change) --- crates/common/ucs_env/src/logger/setup.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/common/ucs_env/src/logger/setup.rs b/crates/common/ucs_env/src/logger/setup.rs index f96b009f83..5463faa461 100644 --- a/crates/common/ucs_env/src/logger/setup.rs +++ b/crates/common/ucs_env/src/logger/setup.rs @@ -49,7 +49,7 @@ pub fn setup( // Disable color or emphasis related ANSI escape codes for JSON formats error_stack::Report::set_color_mode(error_stack::fmt::ColorMode::None); - log_utils::ConsoleLogFormat::PrettyJson + log_utils::ConsoleLogFormat::CompactJson } }; From 76bc7b173ca8790d820267fcf8e19527d4abc02e Mon Sep 17 00:00:00 2001 From: Amitsingh Tanwar Date: Tue, 11 Aug 2026 00:47:36 +0530 Subject: [PATCH 07/12] fix(logs): move CompiledLogFields import outside injector-client feature gate --- crates/common/external-services/src/service.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/common/external-services/src/service.rs b/crates/common/external-services/src/service.rs index 19e73bdd60..ce444007f4 100644 --- a/crates/common/external-services/src/service.rs +++ b/crates/common/external-services/src/service.rs @@ -5,12 +5,13 @@ use common_enums::ApiClientError; #[cfg(feature = "injector-client")] use common_utils::{ consts::{X_API_TAG, X_API_URL, X_SESSION_ID}, - events::{CompiledLogFields, EventStage, MaskedSerdeValue}, + events::{EventStage, MaskedSerdeValue}, request::TransportType, }; #[cfg(all(feature = "injector-client", feature = "log-transformations"))] use common_utils::events::apply_log_fields; use common_utils::{ + events::CompiledLogFields, ext_traits::AsyncExt, lineage, request::{Method, Request, RequestContent}, From b2e6525c1eb9d620aafcacbf981f164f45cfc0b2 Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 19:38:31 +0000 Subject: [PATCH 08/12] chore: auto-fix formatting and generated code Auto-applied by CI: - cargo +nightly fmt --all - make -C sdk generate (if applicable) - make docs (if applicable) This commit was automatically generated by GitHub Actions. --- crates/common/common_utils/src/events.rs | 4 +--- crates/common/external-services/src/service.rs | 4 ++-- crates/grpc-server/grpc-server/src/server/payments.rs | 2 -- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/crates/common/common_utils/src/events.rs b/crates/common/common_utils/src/events.rs index d322c90cf2..14e69bc5f5 100644 --- a/crates/common/common_utils/src/events.rs +++ b/crates/common/common_utils/src/events.rs @@ -673,9 +673,7 @@ fn deep_merge_json(target: &mut serde_json::Value, source: serde_json::Value) { (target, source) { for (key, value) in source_map { - let entry = target_map - .entry(key) - .or_insert(serde_json::Value::Null); + let entry = target_map.entry(key).or_insert(serde_json::Value::Null); if entry.is_object() && value.is_object() { deep_merge_json(entry, value); } else { diff --git a/crates/common/external-services/src/service.rs b/crates/common/external-services/src/service.rs index ce444007f4..fea8bc904a 100644 --- a/crates/common/external-services/src/service.rs +++ b/crates/common/external-services/src/service.rs @@ -2,14 +2,14 @@ use std::{collections::HashMap, str::FromStr, sync::RwLock, time::Duration}; use base64::Engine; use common_enums::ApiClientError; +#[cfg(all(feature = "injector-client", feature = "log-transformations"))] +use common_utils::events::apply_log_fields; #[cfg(feature = "injector-client")] use common_utils::{ consts::{X_API_TAG, X_API_URL, X_SESSION_ID}, events::{EventStage, MaskedSerdeValue}, request::TransportType, }; -#[cfg(all(feature = "injector-client", feature = "log-transformations"))] -use common_utils::events::apply_log_fields; use common_utils::{ events::CompiledLogFields, ext_traits::AsyncExt, diff --git a/crates/grpc-server/grpc-server/src/server/payments.rs b/crates/grpc-server/grpc-server/src/server/payments.rs index c414788068..1fa96a1902 100644 --- a/crates/grpc-server/grpc-server/src/server/payments.rs +++ b/crates/grpc-server/grpc-server/src/server/payments.rs @@ -701,7 +701,6 @@ impl Payments { )) })?; - let event_params = EventProcessingParams { connector_name: &connector.get_connector_name(), service_name, @@ -2736,7 +2735,6 @@ impl PaymentMethod { )) })?; - let event_params = EventProcessingParams { connector_name: &metadata_payload.connector.get_connector_name(), service_name, From 0a19920c409b44811a8349e86fb3a3927ec58146 Mon Sep 17 00:00:00 2001 From: Amitsingh Tanwar Date: Tue, 11 Aug 2026 11:22:42 +0530 Subject: [PATCH 09/12] refactor(deps): update framework-libs-rs to Cow-based Storage keys Update log_utils rev to use Cow<'static, str> keys in Storage instead of String. Adapt apply_log_fields snapshot type and record_value calls. --- Cargo.lock | 4 ++-- crates/common/common_utils/Cargo.toml | 2 +- crates/common/common_utils/src/events.rs | 13 ++++++++----- crates/common/external-services/Cargo.toml | 2 +- crates/common/tracing-kafka/Cargo.toml | 2 +- crates/common/ucs_env/Cargo.toml | 6 +++--- crates/grpc-server/grpc-server/Cargo.toml | 6 +++--- 7 files changed, 19 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c03a528b52..e0458e5bf4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -861,7 +861,7 @@ dependencies = [ [[package]] name = "build_info" version = "0.1.0" -source = "git+https://github.com/juspay/framework-libs-rs?rev=5d0b5824b2a82dafae492f9930c95060507d91b4#5d0b5824b2a82dafae492f9930c95060507d91b4" +source = "git+https://github.com/juspay/framework-libs-rs?rev=76613f5d0c72fd3d26a3403ae859c2a7545e8dd2#76613f5d0c72fd3d26a3403ae859c2a7545e8dd2" dependencies = [ "cargo_metadata 0.23.1", "vergen-gix", @@ -4570,7 +4570,7 @@ dependencies = [ [[package]] name = "log_utils" version = "0.1.0" -source = "git+https://github.com/juspay/framework-libs-rs?rev=5d0b5824b2a82dafae492f9930c95060507d91b4#5d0b5824b2a82dafae492f9930c95060507d91b4" +source = "git+https://github.com/juspay/framework-libs-rs?rev=76613f5d0c72fd3d26a3403ae859c2a7545e8dd2#76613f5d0c72fd3d26a3403ae859c2a7545e8dd2" dependencies = [ "gethostname 1.1.0", "rustc-hash 2.1.3", diff --git a/crates/common/common_utils/Cargo.toml b/crates/common/common_utils/Cargo.toml index 1d218aea63..395e115e68 100644 --- a/crates/common/common_utils/Cargo.toml +++ b/crates/common/common_utils/Cargo.toml @@ -12,7 +12,7 @@ anyhow = "1.0" rdkafka = { version = "0.36", optional = true } tracing-kafka = { path = "../tracing-kafka", optional = true } tracing = { workspace = true } -log_utils = { git = "https://github.com/juspay/framework-libs-rs", rev = "5d0b5824b2a82dafae492f9930c95060507d91b4", package = "log_utils", features = ["tracing", "tracing-storage-api"] } +log_utils = { git = "https://github.com/juspay/framework-libs-rs", rev = "76613f5d0c72fd3d26a3403ae859c2a7545e8dd2", package = "log_utils", features = ["tracing", "tracing-storage-api"] } once_cell = "1.19" chrono = "0.4.31" diff --git a/crates/common/common_utils/src/events.rs b/crates/common/common_utils/src/events.rs index 14e69bc5f5..cde4e4191b 100644 --- a/crates/common/common_utils/src/events.rs +++ b/crates/common/common_utils/src/events.rs @@ -1,6 +1,9 @@ +#[cfg(feature = "log-transformations")] +use std::borrow::Cow; +use std::collections::HashMap; + use hyperswitch_masking::ErasedMaskSerialize; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; use crate::errors::EventPublisherError; use crate::types::ExecutionMode; @@ -590,7 +593,7 @@ pub fn apply_log_fields(compiled: &CompiledLogFields) { } // Snapshot span storage (needed for Source entries to read current values). - let snapshot: Option> = + let snapshot: Option, serde_json::Value>> = log_utils::Storage::with_current_span(|storage| storage.values().clone()); // Build writes: for each rule, resolve the value and group by root key. @@ -650,16 +653,16 @@ pub fn apply_log_fields(compiled: &CompiledLogFields) { for (key, value) in writes { if value.is_object() { // Deep-merge with existing object if present - if let Some(existing) = storage.values().get(&key).cloned() { + if let Some(existing) = storage.values().get(key.as_str()).cloned() { if existing.is_object() { let mut merged = existing; deep_merge_json(&mut merged, value); - storage.record_value(&key, merged); + storage.record_value(key, merged); continue; } } } - storage.record_value(&key, value); + storage.record_value(key, value); } }); } diff --git a/crates/common/external-services/Cargo.toml b/crates/common/external-services/Cargo.toml index 5a2ed4b707..51b5b9774f 100644 --- a/crates/common/external-services/Cargo.toml +++ b/crates/common/external-services/Cargo.toml @@ -59,7 +59,7 @@ injector = { git = "https://github.com/juspay/hyperswitch", tag = "2026.06.18.0" connector_request_kafka = { path = "../connector_request_kafka", optional = true } url = "2.5.0" chrono = "0.4.31" -log_utils = { git = "https://github.com/juspay/framework-libs-rs", rev = "5d0b5824b2a82dafae492f9930c95060507d91b4", package = "log_utils", features = [ +log_utils = { git = "https://github.com/juspay/framework-libs-rs", rev = "76613f5d0c72fd3d26a3403ae859c2a7545e8dd2", package = "log_utils", features = [ "tracing", "tracing-storage-api", ] } diff --git a/crates/common/tracing-kafka/Cargo.toml b/crates/common/tracing-kafka/Cargo.toml index 1242ee77bb..45f51a1f18 100644 --- a/crates/common/tracing-kafka/Cargo.toml +++ b/crates/common/tracing-kafka/Cargo.toml @@ -11,7 +11,7 @@ rdkafka = "0.36" serde_json = { workspace = true } tokio = "1.0" thiserror = { workspace = true } -log_utils = { git = "https://github.com/juspay/framework-libs-rs", rev = "5d0b5824b2a82dafae492f9930c95060507d91b4", package = "log_utils", features = ["tracing"] } +log_utils = { git = "https://github.com/juspay/framework-libs-rs", rev = "76613f5d0c72fd3d26a3403ae859c2a7545e8dd2", package = "log_utils", features = ["tracing"] } # Optional dependencies for metrics prometheus = { version = "0.13", optional = true } diff --git a/crates/common/ucs_env/Cargo.toml b/crates/common/ucs_env/Cargo.toml index d714b70f1a..700d778a39 100644 --- a/crates/common/ucs_env/Cargo.toml +++ b/crates/common/ucs_env/Cargo.toml @@ -49,16 +49,16 @@ tokio = { version = "1.48.0", features = [ tonic = { workspace = true } tonic-reflection = "0.14.0" http = "1.2.0" -log_utils = { git = "https://github.com/juspay/framework-libs-rs", rev = "5d0b5824b2a82dafae492f9930c95060507d91b4", package = "log_utils", features = [ +log_utils = { git = "https://github.com/juspay/framework-libs-rs", rev = "76613f5d0c72fd3d26a3403ae859c2a7545e8dd2", package = "log_utils", features = [ "tracing", ] } -build_info = { git = "https://github.com/juspay/framework-libs-rs", rev = "5d0b5824b2a82dafae492f9930c95060507d91b4", package = "build_info", features = [ +build_info = { git = "https://github.com/juspay/framework-libs-rs", rev = "76613f5d0c72fd3d26a3403ae859c2a7545e8dd2", package = "build_info", features = [ "cargo-workspace", "framework-libs-members-env", ] } [build-dependencies] -build_info = { git = "https://github.com/juspay/framework-libs-rs", rev = "5d0b5824b2a82dafae492f9930c95060507d91b4", package = "build_info", features = [ +build_info = { git = "https://github.com/juspay/framework-libs-rs", rev = "76613f5d0c72fd3d26a3403ae859c2a7545e8dd2", package = "build_info", features = [ "cargo-workspace-build", "vergen-gix-build", "framework-libs-members-env", diff --git a/crates/grpc-server/grpc-server/Cargo.toml b/crates/grpc-server/grpc-server/Cargo.toml index 43887e058f..bf1975e16b 100644 --- a/crates/grpc-server/grpc-server/Cargo.toml +++ b/crates/grpc-server/grpc-server/Cargo.toml @@ -66,11 +66,11 @@ prost-types = "0.14" rustc-hash = "2.0" gethostname = "0.5.0" once_cell = "1.19.0" -log_utils = { git = "https://github.com/juspay/framework-libs-rs", rev = "5d0b5824b2a82dafae492f9930c95060507d91b4", package = "log_utils", features = [ +log_utils = { git = "https://github.com/juspay/framework-libs-rs", rev = "76613f5d0c72fd3d26a3403ae859c2a7545e8dd2", package = "log_utils", features = [ "tracing", "tracing-storage-api", ] } -build_info = { git = "https://github.com/juspay/framework-libs-rs", rev = "5d0b5824b2a82dafae492f9930c95060507d91b4", package = "build_info", features = [ +build_info = { git = "https://github.com/juspay/framework-libs-rs", rev = "76613f5d0c72fd3d26a3403ae859c2a7545e8dd2", package = "build_info", features = [ "cargo-workspace", "framework-libs-members-env", ] } @@ -78,7 +78,7 @@ uuid = { workspace = true } chrono = "0.4.31" [build-dependencies] -build_info = { git = "https://github.com/juspay/framework-libs-rs", rev = "5d0b5824b2a82dafae492f9930c95060507d91b4", package = "build_info", features = [ +build_info = { git = "https://github.com/juspay/framework-libs-rs", rev = "76613f5d0c72fd3d26a3403ae859c2a7545e8dd2", package = "build_info", features = [ "cargo-workspace-build", "vergen-gix-build", "framework-libs-members-env", From 248c3c59d744095f91149c256740213ec14cbcb8 Mon Sep 17 00:00:00 2001 From: Amitsingh Tanwar Date: Tue, 11 Aug 2026 15:05:39 +0530 Subject: [PATCH 10/12] ci: enable log-transformations feature in test commands Add grpc-server/log-transformations to both nextest run commands so CI tests compile and run with log field transformation code enabled. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1514d80e47..20921e8641 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -547,7 +547,7 @@ jobs: done echo "🔍 Connector-only PR — scoping tests to: $CONNECTOR_NAMES" cargo nextest run \ - --features grpc-server/connector-request-kafka \ + --features grpc-server/connector-request-kafka,grpc-server/log-transformations \ --config-file .nextest.toml \ --profile ci \ --no-tests=warn \ @@ -555,7 +555,7 @@ jobs: else echo "🔍 Running full test suite (event=$EVENT)" cargo nextest run \ - --features grpc-server/connector-request-kafka \ + --features grpc-server/connector-request-kafka,grpc-server/log-transformations \ --config-file .nextest.toml \ --profile ci \ -E "$SCHEMA_EXCLUDE" From d34f556bfb896bff58bb2439bd6bae75a9ad56fe Mon Sep 17 00:00:00 2001 From: Amitsingh Tanwar Date: Tue, 11 Aug 2026 16:42:27 +0530 Subject: [PATCH 11/12] feat(logs): add runtime enabled flag for log-transformations Add `enabled` field to `LogFields` config (default: false) so log field application can be toggled at runtime via config or x-config-override without rebuilding. Also add log-transformations feature to Dockerfile. Co-Authored-By: Claude Opus 4.6 --- Dockerfile | 4 ++-- crates/common/common_utils/src/events.rs | 5 +++++ crates/common/external-services/src/service.rs | 6 +++++- crates/common/ucs_env/src/configs.rs | 1 + crates/common/ucs_env/src/logger/config.rs | 9 +++++++++ .../grpc-server/src/server/disputes.rs | 2 ++ .../grpc-server/grpc-server/src/server/events.rs | 6 ++++++ .../grpc-server/src/server/payments.rs | 12 ++++++++++-- crates/grpc-server/grpc-server/src/utils.rs | 16 ++++++++++++---- 9 files changed, 52 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9ea7e4a5e5..78e9de4b9d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -44,7 +44,7 @@ ENV SCCACHE_CACHE_SIZE=5G # Cook dependencies using cargo-chef with caching RUN --mount=type=cache,target=/sccache \ - cargo chef cook --release --features kafka,connector-request-kafka,otel --recipe-path recipe.json + cargo chef cook --release --features kafka,connector-request-kafka,otel,log-transformations --recipe-path recipe.json # Install additional build-time dependencies RUN apt-get update \ @@ -57,7 +57,7 @@ RUN apt-get update \ # Build only the binary shipped by the runtime stage; skips test/SDK crates. COPY . . RUN --mount=type=cache,target=/sccache \ - cargo build --release --features kafka,connector-request-kafka,otel -p grpc-server + cargo build --release --features kafka,connector-request-kafka,otel,log-transformations -p grpc-server # Output sccache statistics RUN sccache --show-stats diff --git a/crates/common/common_utils/src/events.rs b/crates/common/common_utils/src/events.rs index cde4e4191b..05d33c8b10 100644 --- a/crates/common/common_utils/src/events.rs +++ b/crates/common/common_utils/src/events.rs @@ -562,16 +562,21 @@ impl CompiledLogFields { /// Holds compiled fields for both golden log lines. #[derive(Debug, Clone, Default)] pub struct CompiledLogFieldsConfig { + /// Runtime kill-switch: when `false`, `apply_log_fields` is skipped even + /// though the `log-transformations` feature is compiled in. + pub enabled: bool, pub incoming: CompiledLogFields, pub outgoing: CompiledLogFields, } impl CompiledLogFieldsConfig { pub fn compile( + enabled: bool, incoming: &HashMap, outgoing: &HashMap, ) -> Self { Self { + enabled, incoming: CompiledLogFields::compile(incoming), outgoing: CompiledLogFields::compile(outgoing), } diff --git a/crates/common/external-services/src/service.rs b/crates/common/external-services/src/service.rs index fea8bc904a..b26c30d60d 100644 --- a/crates/common/external-services/src/service.rs +++ b/crates/common/external-services/src/service.rs @@ -554,6 +554,8 @@ pub struct EventProcessingParams<'a> { pub merchant_id: &'a str, pub return_raw_connector_data: bool, pub connector_latency: ConnectorLatencyTracker, + /// Runtime kill-switch for log field application. + pub log_fields_enabled: bool, /// Pre-compiled log fields (transformations + static values) for golden log lines. pub log_fields: &'a CompiledLogFields, } @@ -1081,7 +1083,9 @@ where tracing::Span::current().record("latency", elapsed); // Apply outgoing log fields (transformations + static values) before emitting the golden log line #[cfg(feature = "log-transformations")] - apply_log_fields(event_params.log_fields); + if event_params.log_fields_enabled { + apply_log_fields(event_params.log_fields); + } tracing::info!(tag = ?Tag::OutgoingApi, log_type = "api", "Outgoing Request completed"); result_with_integrity_check } diff --git a/crates/common/ucs_env/src/configs.rs b/crates/common/ucs_env/src/configs.rs index 426de44cc9..0ec48e28fd 100644 --- a/crates/common/ucs_env/src/configs.rs +++ b/crates/common/ucs_env/src/configs.rs @@ -346,6 +346,7 @@ impl Config { #[cfg(feature = "log-transformations")] { self.log_fields = Arc::new(CompiledLogFieldsConfig::compile( + self.log.fields.enabled, &self.log.fields.incoming, &self.log.fields.outgoing, )); diff --git a/crates/common/ucs_env/src/logger/config.rs b/crates/common/ucs_env/src/logger/config.rs index 47f37222ef..c9a7938b69 100644 --- a/crates/common/ucs_env/src/logger/config.rs +++ b/crates/common/ucs_env/src/logger/config.rs @@ -17,6 +17,11 @@ use serde::{Deserialize, Serialize}; /// override entries are added to / replace matching base entries; unmentioned entries stay. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] pub struct LogFields { + /// Runtime toggle: when `false`, compiled log fields are not applied + /// even though the `log-transformations` feature is compiled in. + /// Defaults to `false` — must be explicitly enabled in config. + #[serde(default)] + pub enabled: bool, #[serde(default)] pub incoming: HashMap, #[serde(default)] @@ -29,12 +34,16 @@ pub struct LogFields { /// new keys are added, existing keys are overwritten, absent keys are kept. #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct LogFieldsPatch { + pub enabled: Option, pub incoming: Option>, pub outgoing: Option>, } impl Patch for LogFields { fn apply(&mut self, patch: LogFieldsPatch) { + if let Some(enabled) = patch.enabled { + self.enabled = enabled; + } if let Some(incoming) = patch.incoming { self.incoming.extend(incoming); } diff --git a/crates/grpc-server/grpc-server/src/server/disputes.rs b/crates/grpc-server/grpc-server/src/server/disputes.rs index 4a59825519..9ed6c146ff 100644 --- a/crates/grpc-server/grpc-server/src/server/disputes.rs +++ b/crates/grpc-server/grpc-server/src/server/disputes.rs @@ -185,6 +185,7 @@ impl DisputeService for Disputes { merchant_id: merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency, + log_fields_enabled: config.log_fields.enabled, log_fields: &config.log_fields.outgoing, }; @@ -426,6 +427,7 @@ impl DisputeService for Disputes { merchant_id: merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency, + log_fields_enabled: config.log_fields.enabled, log_fields: &config.log_fields.outgoing, }; diff --git a/crates/grpc-server/grpc-server/src/server/events.rs b/crates/grpc-server/grpc-server/src/server/events.rs index ad33fc7c7f..e5bbd55eb0 100644 --- a/crates/grpc-server/grpc-server/src/server/events.rs +++ b/crates/grpc-server/grpc-server/src/server/events.rs @@ -505,6 +505,7 @@ impl EventServiceImpl { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), + log_fields_enabled: config.log_fields.enabled, log_fields: &config.log_fields.outgoing, }; @@ -615,6 +616,7 @@ impl EventServiceImpl { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), + log_fields_enabled: config.log_fields.enabled, log_fields: &config.log_fields.outgoing, }; @@ -725,6 +727,7 @@ impl EventServiceImpl { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), + log_fields_enabled: config.log_fields.enabled, log_fields: &config.log_fields.outgoing, }; @@ -832,6 +835,7 @@ impl EventServiceImpl { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), + log_fields_enabled: config.log_fields.enabled, log_fields: &config.log_fields.outgoing, }; @@ -939,6 +943,7 @@ impl EventServiceImpl { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), + log_fields_enabled: config.log_fields.enabled, log_fields: &config.log_fields.outgoing, }; @@ -1035,6 +1040,7 @@ async fn verify_webhook_source_external( merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), + log_fields_enabled: config.log_fields.enabled, log_fields: &config.log_fields.outgoing, }; diff --git a/crates/grpc-server/grpc-server/src/server/payments.rs b/crates/grpc-server/grpc-server/src/server/payments.rs index 1fa96a1902..ee56babf66 100644 --- a/crates/grpc-server/grpc-server/src/server/payments.rs +++ b/crates/grpc-server/grpc-server/src/server/payments.rs @@ -579,6 +579,7 @@ impl Payments { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), + log_fields_enabled: config.log_fields.enabled, log_fields: &config.log_fields.outgoing, }; @@ -718,6 +719,7 @@ impl Payments { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), + log_fields_enabled: config.log_fields.enabled, log_fields: &config.log_fields.outgoing, }; @@ -1134,7 +1136,8 @@ impl PaymentService for Payments { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), - log_fields: &config.log_fields.outgoing, + log_fields_enabled: config.log_fields.enabled, + log_fields: &config.log_fields.outgoing, }; // handle_response field removed from proto (field 5 reserved) @@ -2614,6 +2617,7 @@ impl PaymentMethod { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), + log_fields_enabled: config.log_fields.enabled, log_fields: &config.log_fields.outgoing, }; @@ -2752,6 +2756,7 @@ impl PaymentMethod { return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), runtime_metadata: &config.runtime_metadata, + log_fields_enabled: config.log_fields.enabled, log_fields: &config.log_fields.outgoing, }; @@ -2870,6 +2875,7 @@ impl MerchantAuthentication { merchant_id: event_params.merchant_id, return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: event_params.connector_latency.clone(), + log_fields_enabled: config.log_fields.enabled, log_fields: &config.log_fields.outgoing, }; @@ -3013,6 +3019,7 @@ impl MerchantAuthentication { merchant_id: event_params.merchant_id, return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: event_params.connector_latency.clone(), + log_fields_enabled: config.log_fields.enabled, log_fields: &config.log_fields.outgoing, }; @@ -3594,7 +3601,8 @@ impl RecurringPaymentService for RecurringPayments { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), - log_fields: &config.log_fields.outgoing, + log_fields_enabled: config.log_fields.enabled, + log_fields: &config.log_fields.outgoing, }; let response = Box::pin( diff --git a/crates/grpc-server/grpc-server/src/utils.rs b/crates/grpc-server/grpc-server/src/utils.rs index a3c07b2657..cf3bcdfbfc 100644 --- a/crates/grpc-server/grpc-server/src/utils.rs +++ b/crates/grpc-server/grpc-server/src/utils.rs @@ -293,6 +293,7 @@ where pub fn log_after_initialization( result: &Result, tonic::Status>, + log_fields_enabled: bool, log_fields: &CompiledLogFields, ) where T: serde::Serialize + std::fmt::Debug, @@ -332,9 +333,14 @@ pub fn log_after_initialization( } // Apply unified log fields (transformations + static values) before emitting the golden log line #[cfg(feature = "log-transformations")] - apply_log_fields(log_fields); + if log_fields_enabled { + apply_log_fields(log_fields); + } #[cfg(not(feature = "log-transformations"))] - let _ = log_fields; + { + let _ = log_fields; + let _ = log_fields_enabled; + } tracing::info!("Golden Log Line (incoming - response)"); } @@ -385,7 +391,7 @@ where .await; let grpc_response = handler_result.into_grpc_status(); - log_after_initialization(&grpc_response, &config.log_fields.incoming); + log_after_initialization(&grpc_response, config.log_fields.enabled, &config.log_fields.incoming); #[cfg(feature = "otel")] observe_internal_latency( @@ -449,7 +455,7 @@ where .await; let grpc_response = handler_result.into_grpc_status(); - log_after_initialization(&grpc_response, &config.log_fields.incoming); + log_after_initialization(&grpc_response, config.log_fields.enabled, &config.log_fields.incoming); #[cfg(feature = "otel")] observe_internal_latency( @@ -719,6 +725,7 @@ macro_rules! implement_connector_operation { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), + log_fields_enabled: config.log_fields.enabled, log_fields: &config.log_fields.outgoing, }; @@ -1081,6 +1088,7 @@ macro_rules! implement_connector_operation { merchant_id: metadata_payload.merchant_id.as_str(), return_raw_connector_data: config.common.return_raw_connector_data, connector_latency: metadata_payload.connector_latency.clone(), + log_fields_enabled: config.log_fields.enabled, log_fields: &config.log_fields.outgoing, }; let call_connector_action = connector_integration.get_call_connector_action(); From fef941047e5ee83ddb650d34dc517e0a598e1eaf Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Tue, 11 Aug 2026 11:26:07 +0000 Subject: [PATCH 12/12] chore: auto-fix formatting and generated code Auto-applied by CI: - cargo +nightly fmt --all - make -C sdk generate (if applicable) - make docs (if applicable) This commit was automatically generated by GitHub Actions. --- crates/grpc-server/grpc-server/src/utils.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/grpc-server/grpc-server/src/utils.rs b/crates/grpc-server/grpc-server/src/utils.rs index cf3bcdfbfc..8bc099a432 100644 --- a/crates/grpc-server/grpc-server/src/utils.rs +++ b/crates/grpc-server/grpc-server/src/utils.rs @@ -391,7 +391,11 @@ where .await; let grpc_response = handler_result.into_grpc_status(); - log_after_initialization(&grpc_response, config.log_fields.enabled, &config.log_fields.incoming); + log_after_initialization( + &grpc_response, + config.log_fields.enabled, + &config.log_fields.incoming, + ); #[cfg(feature = "otel")] observe_internal_latency( @@ -455,7 +459,11 @@ where .await; let grpc_response = handler_result.into_grpc_status(); - log_after_initialization(&grpc_response, config.log_fields.enabled, &config.log_fields.incoming); + log_after_initialization( + &grpc_response, + config.log_fields.enabled, + &config.log_fields.incoming, + ); #[cfg(feature = "otel")] observe_internal_latency(