@kabran-tecnologia/kabran-config 1.9.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kabran-tecnologia/kabran-config",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
4
  "description": "Shared quality configurations, enforcement scripts, and CI/CD tooling for Kabran projects",
5
5
  "author": "Kabran",
6
6
  "license": "MIT",
@@ -590,6 +590,279 @@ export_ci_data() {
590
590
  log_debug "CI data exported to: $output_file"
591
591
  }
592
592
 
593
+ # ==============================================================================
594
+ # OpenTelemetry Metrics Export
595
+ # ==============================================================================
596
+
597
+ # Export CI metrics to OTel Collector via OTLP HTTP
598
+ # Usage: export_ci_metrics_to_otel "$CI_DATA_FILE"
599
+ # Environment: OTEL_ENDPOINT must be set (e.g., http://localhost:4318)
600
+ # Returns: 0 on success, 1 on failure (but never fails the build due to || true usage)
601
+ export_ci_metrics_to_otel() {
602
+ local ci_data_file="${1:-}"
603
+
604
+ # Check if OTEL_ENDPOINT is configured
605
+ if [ -z "${OTEL_ENDPOINT:-}" ]; then
606
+ log_debug "OTEL_ENDPOINT not set, skipping metrics export"
607
+ return 0
608
+ fi
609
+
610
+ # Validate input file
611
+ if [ -z "$ci_data_file" ] || [ ! -f "$ci_data_file" ]; then
612
+ log_warn "CI data file not found: $ci_data_file"
613
+ return 1
614
+ fi
615
+
616
+ # Check for curl
617
+ if ! command -v curl &>/dev/null; then
618
+ log_warn "curl not available, skipping OTel metrics export"
619
+ return 1
620
+ fi
621
+
622
+ log_info "Exporting CI metrics to OTel Collector..."
623
+ log_debug " Endpoint: $OTEL_ENDPOINT"
624
+ log_debug " Data file: $ci_data_file"
625
+
626
+ # Extract metrics from CI data
627
+ local total_ms steps_json project_name trace_id ci_passed
628
+ total_ms=$(jq -r '.timing.total_ms // 0' "$ci_data_file" 2>/dev/null)
629
+ steps_json=$(jq -c '.steps // []' "$ci_data_file" 2>/dev/null)
630
+ project_name=$(jq -r '.project.name // "unknown"' "$ci_data_file" 2>/dev/null)
631
+ trace_id=$(jq -r '.trace_context.trace_id // ""' "$ci_data_file" 2>/dev/null)
632
+
633
+ # Determine overall status
634
+ local failed_count
635
+ failed_count=$(echo "$steps_json" | jq '[.[] | select(.status == "fail")] | length' 2>/dev/null || echo "0")
636
+ if [ "$failed_count" -gt 0 ]; then
637
+ ci_passed="false"
638
+ else
639
+ ci_passed="true"
640
+ fi
641
+
642
+ # Get timestamp in nanoseconds (Unix epoch)
643
+ local timestamp_ns
644
+ timestamp_ns=$(date +%s)000000000
645
+
646
+ # Build OTLP metrics payload
647
+ local otlp_payload
648
+ otlp_payload=$(build_otlp_metrics_payload \
649
+ "$project_name" \
650
+ "$total_ms" \
651
+ "$ci_passed" \
652
+ "$steps_json" \
653
+ "$timestamp_ns" \
654
+ "$trace_id")
655
+
656
+ if [ -z "$otlp_payload" ] || [ "$otlp_payload" = "null" ]; then
657
+ log_warn "Failed to build OTLP payload"
658
+ return 1
659
+ fi
660
+
661
+ # Send to OTel Collector with aggressive timeouts
662
+ # --connect-timeout 1: max 1 second to establish connection
663
+ # --max-time 5: max 5 seconds total for the request
664
+ # -f: fail silently on HTTP errors
665
+ local otel_metrics_endpoint="${OTEL_ENDPOINT}/v1/metrics"
666
+
667
+ log_debug "Sending metrics to: $otel_metrics_endpoint"
668
+
669
+ local http_code
670
+ http_code=$(curl -s -o /dev/null -w "%{http_code}" \
671
+ --connect-timeout 1 \
672
+ --max-time 5 \
673
+ -X POST \
674
+ -H "Content-Type: application/json" \
675
+ -d "$otlp_payload" \
676
+ "$otel_metrics_endpoint" 2>/dev/null) || {
677
+ log_warn "Failed to send metrics to OTel Collector (connection error)"
678
+ return 1
679
+ }
680
+
681
+ if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
682
+ log_success "CI metrics exported to OTel Collector (HTTP $http_code)"
683
+ return 0
684
+ else
685
+ log_warn "OTel Collector returned HTTP $http_code"
686
+ return 1
687
+ fi
688
+ }
689
+
690
+ # Build OTLP JSON payload for metrics
691
+ # Usage: build_otlp_metrics_payload "$project" "$duration_ms" "$passed" "$steps_json" "$timestamp_ns" "$trace_id"
692
+ build_otlp_metrics_payload() {
693
+ local project="$1"
694
+ local duration_ms="$2"
695
+ local passed="$3"
696
+ local steps_json="$4"
697
+ local timestamp_ns="$5"
698
+ local trace_id="${6:-}"
699
+
700
+ # Service attributes
701
+ local service_name="ci-runner"
702
+ local service_version="${CI_CORE_VERSION:-unknown}"
703
+
704
+ # Count steps by status
705
+ local pass_count fail_count skip_count
706
+ pass_count=$(echo "$steps_json" | jq '[.[] | select(.status == "pass")] | length' 2>/dev/null || echo "0")
707
+ fail_count=$(echo "$steps_json" | jq '[.[] | select(.status == "fail")] | length' 2>/dev/null || echo "0")
708
+ skip_count=$(echo "$steps_json" | jq '[.[] | select(.status == "skip")] | length' 2>/dev/null || echo "0")
709
+
710
+ # Build step duration data points
711
+ local step_duration_points
712
+ step_duration_points=$(echo "$steps_json" | jq -c --arg ts "$timestamp_ns" '
713
+ [.[] | select(.status != "skip") | {
714
+ attributes: ([
715
+ {key: "step.name", value: {stringValue: .name}},
716
+ {key: "step.category", value: {stringValue: (.category // "custom")}},
717
+ {key: "step.status", value: {stringValue: .status}}
718
+ ] + (if (.component // "") != "" then [{key: "step.component", value: {stringValue: .component}}] else [] end)),
719
+ startTimeUnixNano: $ts,
720
+ timeUnixNano: $ts,
721
+ asDouble: .duration_ms
722
+ }]
723
+ ' 2>/dev/null || echo "[]")
724
+
725
+ # Build resource attributes (conditionally include trace_id)
726
+ local resource_attributes
727
+ if [ -n "$trace_id" ]; then
728
+ resource_attributes=$(jq -n \
729
+ --arg service_name "$service_name" \
730
+ --arg service_version "$service_version" \
731
+ --arg project "$project" \
732
+ --arg trace_id "$trace_id" \
733
+ '[
734
+ {key: "service.name", value: {stringValue: $service_name}},
735
+ {key: "service.version", value: {stringValue: $service_version}},
736
+ {key: "project.name", value: {stringValue: $project}},
737
+ {key: "trace.id", value: {stringValue: $trace_id}}
738
+ ]')
739
+ else
740
+ resource_attributes=$(jq -n \
741
+ --arg service_name "$service_name" \
742
+ --arg service_version "$service_version" \
743
+ --arg project "$project" \
744
+ '[
745
+ {key: "service.name", value: {stringValue: $service_name}},
746
+ {key: "service.version", value: {stringValue: $service_version}},
747
+ {key: "project.name", value: {stringValue: $project}}
748
+ ]')
749
+ fi
750
+
751
+ # Determine status string
752
+ local status_str="fail"
753
+ if [ "$passed" = "true" ]; then
754
+ status_str="pass"
755
+ fi
756
+
757
+ # Build the full OTLP payload
758
+ jq -n \
759
+ --arg service_version "$service_version" \
760
+ --arg project "$project" \
761
+ --arg timestamp_ns "$timestamp_ns" \
762
+ --arg status_str "$status_str" \
763
+ --argjson duration_ms "$duration_ms" \
764
+ --argjson pass_count "$pass_count" \
765
+ --argjson fail_count "$fail_count" \
766
+ --argjson skip_count "$skip_count" \
767
+ --argjson step_duration_points "$step_duration_points" \
768
+ --argjson resource_attributes "$resource_attributes" \
769
+ '{
770
+ resourceMetrics: [{
771
+ resource: {
772
+ attributes: $resource_attributes
773
+ },
774
+ scopeMetrics: [{
775
+ scope: {
776
+ name: "kabran-config/ci-runner",
777
+ version: $service_version
778
+ },
779
+ metrics: [
780
+ {
781
+ name: "ci.build.duration",
782
+ description: "Total duration of CI build in milliseconds",
783
+ unit: "ms",
784
+ gauge: {
785
+ dataPoints: [{
786
+ attributes: [
787
+ {key: "project", value: {stringValue: $project}},
788
+ {key: "status", value: {stringValue: $status_str}}
789
+ ],
790
+ startTimeUnixNano: $timestamp_ns,
791
+ timeUnixNano: $timestamp_ns,
792
+ asDouble: $duration_ms
793
+ }]
794
+ }
795
+ },
796
+ {
797
+ name: "ci.build.status",
798
+ description: "CI build status counter (1 = occurrence)",
799
+ unit: "1",
800
+ sum: {
801
+ dataPoints: [{
802
+ attributes: [
803
+ {key: "project", value: {stringValue: $project}},
804
+ {key: "status", value: {stringValue: $status_str}}
805
+ ],
806
+ startTimeUnixNano: $timestamp_ns,
807
+ timeUnixNano: $timestamp_ns,
808
+ asInt: "1"
809
+ }],
810
+ aggregationTemporality: 2,
811
+ isMonotonic: true
812
+ }
813
+ },
814
+ {
815
+ name: "ci.step.count",
816
+ description: "Count of CI steps by status",
817
+ unit: "1",
818
+ sum: {
819
+ dataPoints: [
820
+ {
821
+ attributes: [
822
+ {key: "project", value: {stringValue: $project}},
823
+ {key: "status", value: {stringValue: "pass"}}
824
+ ],
825
+ startTimeUnixNano: $timestamp_ns,
826
+ timeUnixNano: $timestamp_ns,
827
+ asInt: ($pass_count | tostring)
828
+ },
829
+ {
830
+ attributes: [
831
+ {key: "project", value: {stringValue: $project}},
832
+ {key: "status", value: {stringValue: "fail"}}
833
+ ],
834
+ startTimeUnixNano: $timestamp_ns,
835
+ timeUnixNano: $timestamp_ns,
836
+ asInt: ($fail_count | tostring)
837
+ },
838
+ {
839
+ attributes: [
840
+ {key: "project", value: {stringValue: $project}},
841
+ {key: "status", value: {stringValue: "skip"}}
842
+ ],
843
+ startTimeUnixNano: $timestamp_ns,
844
+ timeUnixNano: $timestamp_ns,
845
+ asInt: ($skip_count | tostring)
846
+ }
847
+ ],
848
+ aggregationTemporality: 2,
849
+ isMonotonic: false
850
+ }
851
+ },
852
+ {
853
+ name: "ci.step.duration",
854
+ description: "Duration of individual CI steps in milliseconds",
855
+ unit: "ms",
856
+ gauge: {
857
+ dataPoints: $step_duration_points
858
+ }
859
+ }
860
+ ]
861
+ }]
862
+ }]
863
+ }'
864
+ }
865
+
593
866
  # ==============================================================================
594
867
  # JSON Output Generation
595
868
  # ==============================================================================
@@ -31,6 +31,7 @@ Environment Variables:
31
31
  CI_OUTPUT_FILE Output file for legacy v1 format
32
32
  CI_OUTPUT_FILE_V2 Output file for v2 format (default: docs/quality/ci-result.json)
33
33
  CI_CONFIG_FILE Path to project ci-config.sh
34
+ OTEL_ENDPOINT OTel Collector endpoint for metrics export (e.g., http://localhost:4318)
34
35
 
35
36
  Examples:
36
37
  # Run all steps
@@ -232,6 +233,14 @@ if [ "$USE_V2" = "true" ]; then
232
233
  generate_ci_json "$OUTPUT_FILE" "$CI_PASSED" "$FAILED" "$PROJECT_NAME" "$METADATA"
233
234
  fi
234
235
 
236
+ # ==============================================================================
237
+ # Export Metrics to OTel Collector (if configured)
238
+ # ==============================================================================
239
+ # Fail-safe: telemetry failures NEVER fail the build (RN-01)
240
+ if [ -n "${OTEL_ENDPOINT:-}" ]; then
241
+ export_ci_metrics_to_otel "$INTERMEDIATE_FILE" || true
242
+ fi
243
+
235
244
  # Cleanup
236
245
  rm -f "$INTERMEDIATE_FILE"
237
246
  else