Convert opencode transcripts to otel (or agent) traces
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Implement configuration file support with config-rs

- Add config and toml dependencies
- Create layered config system: Defaults > Config Files > ENV > CLI
- Support config loading from multiple paths:
- ~/.config/exp2span/config.toml
- ~/.exp2span.toml
- /etc/exp2span/config.toml
- .exp2span.toml
- Custom path via --config flag
- Environment variable support with EXP2SPAN prefix
- CLI args override config and environment variables
- Merge config with CLI arguments for final configuration

Resolves exs-njr

rektide 611ae978 a756ac0e

+565 -49
+391 -1
Cargo.lock
··· 3 3 version = 4 4 4 5 5 [[package]] 6 + name = "ahash" 7 + version = "0.8.12" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" 10 + dependencies = [ 11 + "cfg-if", 12 + "once_cell", 13 + "version_check", 14 + "zerocopy", 15 + ] 16 + 17 + [[package]] 6 18 name = "aho-corasick" 7 19 version = "1.1.4" 8 20 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 10 22 dependencies = [ 11 23 "memchr", 12 24 ] 25 + 26 + [[package]] 27 + name = "allocator-api2" 28 + version = "0.2.21" 29 + source = "registry+https://github.com/rust-lang/crates.io-index" 30 + checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" 13 31 14 32 [[package]] 15 33 name = "anstream" ··· 66 84 version = "1.0.100" 67 85 source = "registry+https://github.com/rust-lang/crates.io-index" 68 86 checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" 87 + 88 + [[package]] 89 + name = "arraydeque" 90 + version = "0.5.1" 91 + source = "registry+https://github.com/rust-lang/crates.io-index" 92 + checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" 69 93 70 94 [[package]] 71 95 name = "async-stream" ··· 161 185 162 186 [[package]] 163 187 name = "base64" 188 + version = "0.21.7" 189 + source = "registry+https://github.com/rust-lang/crates.io-index" 190 + checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 191 + 192 + [[package]] 193 + name = "base64" 164 194 version = "0.22.1" 165 195 source = "registry+https://github.com/rust-lang/crates.io-index" 166 196 checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" ··· 170 200 version = "2.10.0" 171 201 source = "registry+https://github.com/rust-lang/crates.io-index" 172 202 checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" 203 + dependencies = [ 204 + "serde_core", 205 + ] 206 + 207 + [[package]] 208 + name = "block-buffer" 209 + version = "0.10.4" 210 + source = "registry+https://github.com/rust-lang/crates.io-index" 211 + checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 212 + dependencies = [ 213 + "generic-array", 214 + ] 173 215 174 216 [[package]] 175 217 name = "bumpalo" ··· 266 308 ] 267 309 268 310 [[package]] 311 + name = "config" 312 + version = "0.14.1" 313 + source = "registry+https://github.com/rust-lang/crates.io-index" 314 + checksum = "68578f196d2a33ff61b27fae256c3164f65e36382648e30666dde05b8cc9dfdf" 315 + dependencies = [ 316 + "async-trait", 317 + "convert_case", 318 + "json5", 319 + "nom", 320 + "pathdiff", 321 + "ron", 322 + "rust-ini", 323 + "serde", 324 + "serde_json", 325 + "toml", 326 + "yaml-rust2", 327 + ] 328 + 329 + [[package]] 330 + name = "const-random" 331 + version = "0.1.18" 332 + source = "registry+https://github.com/rust-lang/crates.io-index" 333 + checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" 334 + dependencies = [ 335 + "const-random-macro", 336 + ] 337 + 338 + [[package]] 339 + name = "const-random-macro" 340 + version = "0.1.16" 341 + source = "registry+https://github.com/rust-lang/crates.io-index" 342 + checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" 343 + dependencies = [ 344 + "getrandom", 345 + "once_cell", 346 + "tiny-keccak", 347 + ] 348 + 349 + [[package]] 350 + name = "convert_case" 351 + version = "0.6.0" 352 + source = "registry+https://github.com/rust-lang/crates.io-index" 353 + checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" 354 + dependencies = [ 355 + "unicode-segmentation", 356 + ] 357 + 358 + [[package]] 359 + name = "cpufeatures" 360 + version = "0.2.17" 361 + source = "registry+https://github.com/rust-lang/crates.io-index" 362 + checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 363 + dependencies = [ 364 + "libc", 365 + ] 366 + 367 + [[package]] 269 368 name = "crossterm" 270 369 version = "0.29.0" 271 370 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 289 388 ] 290 389 291 390 [[package]] 391 + name = "crunchy" 392 + version = "0.2.4" 393 + source = "registry+https://github.com/rust-lang/crates.io-index" 394 + checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" 395 + 396 + [[package]] 397 + name = "crypto-common" 398 + version = "0.1.7" 399 + source = "registry+https://github.com/rust-lang/crates.io-index" 400 + checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" 401 + dependencies = [ 402 + "generic-array", 403 + "typenum", 404 + ] 405 + 406 + [[package]] 407 + name = "digest" 408 + version = "0.10.7" 409 + source = "registry+https://github.com/rust-lang/crates.io-index" 410 + checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 411 + dependencies = [ 412 + "block-buffer", 413 + "crypto-common", 414 + ] 415 + 416 + [[package]] 417 + name = "dlv-list" 418 + version = "0.5.2" 419 + source = "registry+https://github.com/rust-lang/crates.io-index" 420 + checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" 421 + dependencies = [ 422 + "const-random", 423 + ] 424 + 425 + [[package]] 292 426 name = "document-features" 293 427 version = "0.2.12" 294 428 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 304 438 checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 305 439 306 440 [[package]] 441 + name = "encoding_rs" 442 + version = "0.8.35" 443 + source = "registry+https://github.com/rust-lang/crates.io-index" 444 + checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 445 + dependencies = [ 446 + "cfg-if", 447 + ] 448 + 449 + [[package]] 307 450 name = "equivalent" 308 451 version = "1.0.2" 309 452 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 328 471 "clap_complete", 329 472 "colored", 330 473 "comfy-table", 474 + "config", 331 475 "home", 332 476 "jiff", 333 477 "opentelemetry", ··· 339 483 "serde_yaml", 340 484 "thiserror", 341 485 "tokio", 486 + "toml", 342 487 "tonic", 343 488 "tracing", 344 489 "tracing-opentelemetry", ··· 416 561 ] 417 562 418 563 [[package]] 564 + name = "generic-array" 565 + version = "0.14.7" 566 + source = "registry+https://github.com/rust-lang/crates.io-index" 567 + checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 568 + dependencies = [ 569 + "typenum", 570 + "version_check", 571 + ] 572 + 573 + [[package]] 419 574 name = "getrandom" 420 575 version = "0.2.17" 421 576 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 459 614 460 615 [[package]] 461 616 name = "hashbrown" 617 + version = "0.14.5" 618 + source = "registry+https://github.com/rust-lang/crates.io-index" 619 + checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 620 + dependencies = [ 621 + "ahash", 622 + "allocator-api2", 623 + ] 624 + 625 + [[package]] 626 + name = "hashbrown" 462 627 version = "0.16.1" 463 628 source = "registry+https://github.com/rust-lang/crates.io-index" 464 629 checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" 630 + 631 + [[package]] 632 + name = "hashlink" 633 + version = "0.8.4" 634 + source = "registry+https://github.com/rust-lang/crates.io-index" 635 + checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" 636 + dependencies = [ 637 + "hashbrown 0.14.5", 638 + ] 465 639 466 640 [[package]] 467 641 name = "heck" ··· 673 847 ] 674 848 675 849 [[package]] 850 + name = "json5" 851 + version = "0.4.1" 852 + source = "registry+https://github.com/rust-lang/crates.io-index" 853 + checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" 854 + dependencies = [ 855 + "pest", 856 + "pest_derive", 857 + "serde", 858 + ] 859 + 860 + [[package]] 676 861 name = "lazy_static" 677 862 version = "1.5.0" 678 863 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 739 924 checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 740 925 741 926 [[package]] 927 + name = "minimal-lexical" 928 + version = "0.2.1" 929 + source = "registry+https://github.com/rust-lang/crates.io-index" 930 + checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 931 + 932 + [[package]] 742 933 name = "mio" 743 934 version = "1.1.1" 744 935 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 750 941 ] 751 942 752 943 [[package]] 944 + name = "nom" 945 + version = "7.1.3" 946 + source = "registry+https://github.com/rust-lang/crates.io-index" 947 + checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 948 + dependencies = [ 949 + "memchr", 950 + "minimal-lexical", 951 + ] 952 + 953 + [[package]] 753 954 name = "nu-ansi-term" 754 955 version = "0.50.3" 755 956 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 854 1055 ] 855 1056 856 1057 [[package]] 1058 + name = "ordered-multimap" 1059 + version = "0.7.3" 1060 + source = "registry+https://github.com/rust-lang/crates.io-index" 1061 + checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" 1062 + dependencies = [ 1063 + "dlv-list", 1064 + "hashbrown 0.14.5", 1065 + ] 1066 + 1067 + [[package]] 857 1068 name = "parking_lot" 858 1069 version = "0.12.5" 859 1070 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 877 1088 ] 878 1089 879 1090 [[package]] 1091 + name = "pathdiff" 1092 + version = "0.2.3" 1093 + source = "registry+https://github.com/rust-lang/crates.io-index" 1094 + checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" 1095 + 1096 + [[package]] 880 1097 name = "percent-encoding" 881 1098 version = "2.3.2" 882 1099 source = "registry+https://github.com/rust-lang/crates.io-index" 883 1100 checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" 884 1101 885 1102 [[package]] 1103 + name = "pest" 1104 + version = "2.8.5" 1105 + source = "registry+https://github.com/rust-lang/crates.io-index" 1106 + checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" 1107 + dependencies = [ 1108 + "memchr", 1109 + "ucd-trie", 1110 + ] 1111 + 1112 + [[package]] 1113 + name = "pest_derive" 1114 + version = "2.8.5" 1115 + source = "registry+https://github.com/rust-lang/crates.io-index" 1116 + checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed" 1117 + dependencies = [ 1118 + "pest", 1119 + "pest_generator", 1120 + ] 1121 + 1122 + [[package]] 1123 + name = "pest_generator" 1124 + version = "2.8.5" 1125 + source = "registry+https://github.com/rust-lang/crates.io-index" 1126 + checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5" 1127 + dependencies = [ 1128 + "pest", 1129 + "pest_meta", 1130 + "proc-macro2", 1131 + "quote", 1132 + "syn", 1133 + ] 1134 + 1135 + [[package]] 1136 + name = "pest_meta" 1137 + version = "2.8.5" 1138 + source = "registry+https://github.com/rust-lang/crates.io-index" 1139 + checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365" 1140 + dependencies = [ 1141 + "pest", 1142 + "sha2", 1143 + ] 1144 + 1145 + [[package]] 886 1146 name = "pin-project" 887 1147 version = "1.1.10" 888 1148 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1048 1308 checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" 1049 1309 1050 1310 [[package]] 1311 + name = "ron" 1312 + version = "0.8.1" 1313 + source = "registry+https://github.com/rust-lang/crates.io-index" 1314 + checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" 1315 + dependencies = [ 1316 + "base64 0.21.7", 1317 + "bitflags", 1318 + "serde", 1319 + "serde_derive", 1320 + ] 1321 + 1322 + [[package]] 1323 + name = "rust-ini" 1324 + version = "0.20.0" 1325 + source = "registry+https://github.com/rust-lang/crates.io-index" 1326 + checksum = "3e0698206bcb8882bf2a9ecb4c1e7785db57ff052297085a6efd4fe42302068a" 1327 + dependencies = [ 1328 + "cfg-if", 1329 + "ordered-multimap", 1330 + ] 1331 + 1332 + [[package]] 1051 1333 name = "rustix" 1052 1334 version = "1.1.3" 1053 1335 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1122 1404 ] 1123 1405 1124 1406 [[package]] 1407 + name = "serde_spanned" 1408 + version = "0.6.9" 1409 + source = "registry+https://github.com/rust-lang/crates.io-index" 1410 + checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" 1411 + dependencies = [ 1412 + "serde", 1413 + ] 1414 + 1415 + [[package]] 1125 1416 name = "serde_yaml" 1126 1417 version = "0.9.34+deprecated" 1127 1418 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1135 1426 ] 1136 1427 1137 1428 [[package]] 1429 + name = "sha2" 1430 + version = "0.10.9" 1431 + source = "registry+https://github.com/rust-lang/crates.io-index" 1432 + checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" 1433 + dependencies = [ 1434 + "cfg-if", 1435 + "cpufeatures", 1436 + "digest", 1437 + ] 1438 + 1439 + [[package]] 1138 1440 name = "sharded-slab" 1139 1441 version = "0.1.7" 1140 1442 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1238 1540 ] 1239 1541 1240 1542 [[package]] 1543 + name = "tiny-keccak" 1544 + version = "2.0.2" 1545 + source = "registry+https://github.com/rust-lang/crates.io-index" 1546 + checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" 1547 + dependencies = [ 1548 + "crunchy", 1549 + ] 1550 + 1551 + [[package]] 1241 1552 name = "tokio" 1242 1553 version = "1.49.0" 1243 1554 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1290 1601 ] 1291 1602 1292 1603 [[package]] 1604 + name = "toml" 1605 + version = "0.8.23" 1606 + source = "registry+https://github.com/rust-lang/crates.io-index" 1607 + checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" 1608 + dependencies = [ 1609 + "serde", 1610 + "serde_spanned", 1611 + "toml_datetime", 1612 + "toml_edit", 1613 + ] 1614 + 1615 + [[package]] 1616 + name = "toml_datetime" 1617 + version = "0.6.11" 1618 + source = "registry+https://github.com/rust-lang/crates.io-index" 1619 + checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" 1620 + dependencies = [ 1621 + "serde", 1622 + ] 1623 + 1624 + [[package]] 1625 + name = "toml_edit" 1626 + version = "0.22.27" 1627 + source = "registry+https://github.com/rust-lang/crates.io-index" 1628 + checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" 1629 + dependencies = [ 1630 + "indexmap 2.13.0", 1631 + "serde", 1632 + "serde_spanned", 1633 + "toml_datetime", 1634 + "toml_write", 1635 + "winnow", 1636 + ] 1637 + 1638 + [[package]] 1639 + name = "toml_write" 1640 + version = "0.1.2" 1641 + source = "registry+https://github.com/rust-lang/crates.io-index" 1642 + checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" 1643 + 1644 + [[package]] 1293 1645 name = "tonic" 1294 1646 version = "0.12.3" 1295 1647 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1298 1650 "async-stream", 1299 1651 "async-trait", 1300 1652 "axum", 1301 - "base64", 1653 + "base64 0.22.1", 1302 1654 "bytes", 1303 1655 "h2", 1304 1656 "http", ··· 1464 1816 checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 1465 1817 1466 1818 [[package]] 1819 + name = "typenum" 1820 + version = "1.19.0" 1821 + source = "registry+https://github.com/rust-lang/crates.io-index" 1822 + checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" 1823 + 1824 + [[package]] 1825 + name = "ucd-trie" 1826 + version = "0.1.7" 1827 + source = "registry+https://github.com/rust-lang/crates.io-index" 1828 + checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" 1829 + 1830 + [[package]] 1467 1831 name = "unicode-ident" 1468 1832 version = "1.0.22" 1469 1833 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1500 1864 checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 1501 1865 1502 1866 [[package]] 1867 + name = "version_check" 1868 + version = "0.9.5" 1869 + source = "registry+https://github.com/rust-lang/crates.io-index" 1870 + checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 1871 + 1872 + [[package]] 1503 1873 name = "want" 1504 1874 version = "0.3.1" 1505 1875 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1752 2122 version = "0.53.1" 1753 2123 source = "registry+https://github.com/rust-lang/crates.io-index" 1754 2124 checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" 2125 + 2126 + [[package]] 2127 + name = "winnow" 2128 + version = "0.7.14" 2129 + source = "registry+https://github.com/rust-lang/crates.io-index" 2130 + checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" 2131 + dependencies = [ 2132 + "memchr", 2133 + ] 2134 + 2135 + [[package]] 2136 + name = "yaml-rust2" 2137 + version = "0.8.1" 2138 + source = "registry+https://github.com/rust-lang/crates.io-index" 2139 + checksum = "8902160c4e6f2fb145dbe9d6760a75e3c9522d8bf796ed7047c85919ac7115f8" 2140 + dependencies = [ 2141 + "arraydeque", 2142 + "encoding_rs", 2143 + "hashlink", 2144 + ] 1755 2145 1756 2146 [[package]] 1757 2147 name = "zerocopy"
+2
Cargo.toml
··· 24 24 thiserror = "1.0" 25 25 comfy-table = "7" 26 26 home = "0.5" 27 + config = "0.14" 28 + toml = "0.8"
+16 -30
src/commands/export.rs
··· 1 1 use anyhow::{Context, Result}; 2 - 3 - use crate::cli::{ExportArgs, OtlpProtocol, OutputFormat}; 4 - use crate::config::{OtlpConfig, OtlpProtocol as ConfigOtlpProtocol}; 2 + use crate::cli::{ExportArgs, OutputFormat}; 3 + use crate::config::AppConfig; 5 4 use crate::parser::LogParser; 6 5 use crate::exporter::OtelExporter; 7 6 use crate::formatter; 7 + 8 8 pub async fn execute(args: ExportArgs, output_format: OutputFormat) -> Result<()> { 9 9 let use_stdout = matches!(output_format, OutputFormat::Json | OutputFormat::JsonLines | OutputFormat::Yaml | OutputFormat::Table); 10 10 11 - let parser = LogParser::new(); 11 + let mut config = AppConfig::load(None).context("Failed to load configuration")?; 12 12 13 - let otlp_config = OtlpConfig { 14 - endpoint: args.otlp_endpoint.clone(), 15 - protocol: match args.otlp_protocol { 16 - OtlpProtocol::Http => ConfigOtlpProtocol::Http, 17 - OtlpProtocol::Grpc => ConfigOtlpProtocol::Grpc, 18 - }, 19 - headers: args.otlp_header.into_iter().collect(), 20 - service_name: args.otlp_service_name.clone(), 21 - batch_size: args.batch_size, 22 - timeout: std::time::Duration::from_secs(args.otlp_timeout_secs), 23 - max_retries: args.otlp_max_retries, 24 - retry_delay: std::time::Duration::from_millis(args.otlp_retry_delay_ms), 25 - }; 13 + config.otel.merge_from_args(&args); 26 14 27 15 tracing::info!( 28 16 "OTLP configuration: {} -> {}", 29 - otlp_config.protocol as u8, 30 - otlp_config.endpoint 17 + config.otel.protocol, 18 + config.otel.endpoint 31 19 ); 20 + 21 + let parser = LogParser::new(); 32 22 33 23 if args.dry_run { 34 24 let log = parser.parse_file(args.file.to_str().unwrap())?; 35 25 let entries = parser.parse_entries(&log); 36 26 37 - let filtered_entries = entries; 27 + tracing::info!("Dry run mode - {} spans would be exported", entries.len()); 38 28 39 - tracing::info!("Dry run mode - {} spans would be exported", filtered_entries.len()); 40 - 41 - let output = formatter::format_spans(&filtered_entries, output_format)?; 29 + let output = formatter::format_spans(&entries, output_format)?; 42 30 println!("{}", output); 43 31 return Ok(()); 44 32 } ··· 46 34 let log = parser.parse_file(args.file.to_str().unwrap())?; 47 35 let entries = parser.parse_entries(&log); 48 36 49 - let filtered_entries = entries; 50 - 51 37 if use_stdout { 52 - tracing::info!("Outputting {} spans to stdout in {:?} format", filtered_entries.len(), output_format); 53 - let output = formatter::format_spans(&filtered_entries, output_format)?; 38 + tracing::info!("Outputting {} spans to stdout in {:?} format", entries.len(), output_format); 39 + let output = formatter::format_spans(&entries, output_format)?; 54 40 println!("{}", output); 55 41 return Ok(()); 56 42 } 57 43 58 - let exporter = OtelExporter::new(otlp_config.clone()) 44 + let exporter = OtelExporter::new(config.otel.clone()) 59 45 .await 60 46 .context("Failed to create OTLP exporter")?; 61 47 62 48 tracing::info!("Created OTLP exporter: {}", exporter.get_endpoint()); 63 49 64 - tracing::info!("Exporting {} spans...", filtered_entries.len()); 50 + tracing::info!("Exporting {} spans...", entries.len()); 65 51 66 - for entry in &filtered_entries { 52 + for entry in &entries { 67 53 exporter.export_log(entry); 68 54 } 69 55
+156 -18
src/config.rs
··· 1 - use anyhow::Result; 1 + use anyhow::{Context, Result}; 2 + use cfg::{Config, Environment, File}; 3 + use config as cfg; 4 + use serde::Deserialize; 2 5 use std::collections::HashMap; 6 + use std::path::PathBuf; 7 + use std::str::FromStr; 3 8 use std::time::Duration; 4 9 5 - #[derive(Debug, Clone)] 10 + #[derive(Debug, Clone, Copy, PartialEq, Deserialize)] 11 + #[serde(rename_all = "lowercase")] 12 + pub enum OtlpProtocol { 13 + Http, 14 + Grpc, 15 + } 16 + 17 + impl std::fmt::Display for OtlpProtocol { 18 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 19 + match self { 20 + OtlpProtocol::Http => write!(f, "http"), 21 + OtlpProtocol::Grpc => write!(f, "grpc"), 22 + } 23 + } 24 + } 25 + 26 + impl FromStr for OtlpProtocol { 27 + type Err = String; 28 + 29 + fn from_str(s: &str) -> Result<Self, Self::Err> { 30 + match s.to_lowercase().as_str() { 31 + "http" => Ok(OtlpProtocol::Http), 32 + "grpc" => Ok(OtlpProtocol::Grpc), 33 + _ => Err(format!("Invalid protocol: {}", s)), 34 + } 35 + } 36 + } 37 + 38 + #[derive(Debug, Clone, Deserialize)] 39 + #[serde(default)] 6 40 pub struct OtlpConfig { 7 41 pub endpoint: String, 8 42 pub protocol: OtlpProtocol, 9 43 pub headers: HashMap<String, String>, 44 + #[serde(rename = "service_name")] 10 45 pub service_name: String, 11 46 pub batch_size: usize, 12 47 pub timeout: Duration, 48 + #[serde(rename = "max_retries")] 13 49 pub max_retries: u32, 50 + #[serde(rename = "retry_delay")] 14 51 pub retry_delay: Duration, 15 52 } 16 53 17 - #[derive(Debug, Clone, Copy, PartialEq)] 18 - pub enum OtlpProtocol { 19 - Http, 20 - Grpc, 21 - } 22 - 23 54 impl Default for OtlpConfig { 24 55 fn default() -> Self { 25 56 Self { ··· 35 66 } 36 67 } 37 68 38 - #[derive(Debug, Clone)] 39 - pub struct Config { 40 - pub otlp: OtlpConfig, 41 - pub output_format: String, 69 + impl OtlpConfig { 70 + pub fn merge_from_args(&mut self, args: &crate::cli::ExportArgs) { 71 + if args.otlp_endpoint != "http://localhost:4318" { 72 + self.endpoint = args.otlp_endpoint.clone(); 73 + } 74 + if args.otlp_protocol != crate::cli::OtlpProtocol::Http { 75 + self.protocol = OtlpProtocol::Http; 76 + } 77 + if args.otlp_protocol != crate::cli::OtlpProtocol::Grpc { 78 + self.protocol = OtlpProtocol::Grpc; 79 + } 80 + if !args.otlp_header.is_empty() { 81 + self.headers = args.otlp_header.clone().into_iter().collect(); 82 + } 83 + if args.otlp_service_name != "exp2span" { 84 + self.service_name = args.otlp_service_name.clone(); 85 + } 86 + if args.batch_size != 100 { 87 + self.batch_size = args.batch_size; 88 + } 89 + if args.otlp_timeout_secs != 30 { 90 + self.timeout = Duration::from_secs(args.otlp_timeout_secs); 91 + } 92 + if args.otlp_max_retries != 3 { 93 + self.max_retries = args.otlp_max_retries; 94 + } 95 + if args.otlp_retry_delay_ms != 100 { 96 + self.retry_delay = Duration::from_millis(args.otlp_retry_delay_ms); 97 + } 98 + } 99 + } 100 + 101 + #[derive(Debug, Clone, Deserialize)] 102 + #[serde(default)] 103 + pub struct OutputConfig { 104 + pub format: String, 42 105 pub include_thinking: bool, 106 + pub compact: bool, 43 107 } 44 108 45 - impl Default for Config { 109 + impl Default for OutputConfig { 46 110 fn default() -> Self { 47 111 Self { 48 - otlp: OtlpConfig::default(), 49 - output_format: "json".to_string(), 112 + format: "otlp".to_string(), 50 113 include_thinking: false, 114 + compact: false, 51 115 } 52 116 } 53 117 } 54 118 55 - impl Config { 56 - pub fn load(_cli_path: Option<&std::path::Path>) -> Result<Self> { 57 - Ok(Self::default()) 119 + #[derive(Debug, Clone, Deserialize)] 120 + #[serde(default)] 121 + pub struct ParserConfig { 122 + pub strict_mode: bool, 123 + #[serde(rename = "max_file_size_mb")] 124 + pub max_file_size_mb: Option<u64>, 125 + } 126 + 127 + impl Default for ParserConfig { 128 + fn default() -> Self { 129 + Self { 130 + strict_mode: false, 131 + max_file_size_mb: None, 132 + } 133 + } 134 + } 135 + 136 + #[derive(Debug, Clone, Deserialize)] 137 + #[serde(default)] 138 + pub struct AppConfig { 139 + pub otel: OtlpConfig, 140 + pub output: OutputConfig, 141 + pub parser: ParserConfig, 142 + } 143 + 144 + impl Default for AppConfig { 145 + fn default() -> Self { 146 + Self { 147 + otel: OtlpConfig::default(), 148 + output: OutputConfig::default(), 149 + parser: ParserConfig::default(), 150 + } 151 + } 152 + } 153 + 154 + impl AppConfig { 155 + pub fn load(cli_config_path: Option<&PathBuf>) -> Result<Self> { 156 + let mut builder = Config::builder(); 157 + 158 + for path in Self::default_paths() { 159 + if path.exists() { 160 + builder = builder.add_source(File::from(path.as_path()).required(false)); 161 + } 162 + } 163 + 164 + if let Some(path) = cli_config_path { 165 + if path.exists() { 166 + builder = builder.add_source(File::from(path.as_path())); 167 + } 168 + } 169 + 170 + builder = builder.add_source( 171 + Environment::with_prefix("EXP2SPAN") 172 + .prefix_separator("_") 173 + .separator("__") 174 + .try_parsing(true), 175 + ); 176 + 177 + let config = builder.build().context("Failed to build configuration")?; 178 + 179 + config 180 + .try_deserialize() 181 + .context("Failed to deserialize configuration") 182 + } 183 + 184 + fn default_paths() -> Vec<PathBuf> { 185 + let mut paths = Vec::new(); 186 + 187 + if let Some(home) = home::home_dir() { 188 + paths.push(home.join(".config/exp2span/config.toml")); 189 + paths.push(home.join(".exp2span.toml")); 190 + } 191 + 192 + paths.push(PathBuf::from("/etc/exp2span/config.toml")); 193 + paths.push(PathBuf::from(".exp2span.toml")); 194 + 195 + paths 58 196 } 59 197 }