logbrew-sdk 0.1.1 → 0.1.2
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.
- checksums.yaml +4 -4
- data/README.md +211 -3
- data/examples/Makefile +13 -1
- data/examples/automatic_delivery.rb +32 -0
- data/examples/persistent_worker_delivery.rb +33 -0
- data/examples/sidekiq_tracing.rb +22 -0
- data/lib/logbrew/automatic_delivery.rb +493 -0
- data/lib/logbrew/bounded_event_queue.rb +196 -0
- data/lib/logbrew/event_batcher.rb +67 -0
- data/lib/logbrew/faraday_tracing.rb +55 -0
- data/lib/logbrew/http_client_tracing.rb +282 -0
- data/lib/logbrew/operation_tracing.rb +16 -1
- data/lib/logbrew/persistent_event_store.rb +403 -0
- data/lib/logbrew/sidekiq.rb +466 -0
- data/lib/logbrew/span_events.rb +34 -0
- data/lib/logbrew/worker_lifecycle.rb +211 -0
- data/lib/logbrew.rb +357 -33
- metadata +14 -2
data/lib/logbrew.rb
CHANGED
|
@@ -39,6 +39,10 @@ module LogBrew
|
|
|
39
39
|
end
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
+
require_relative "logbrew/event_batcher"
|
|
43
|
+
require_relative "logbrew/persistent_event_store"
|
|
44
|
+
require_relative "logbrew/bounded_event_queue"
|
|
45
|
+
|
|
42
46
|
class TransportError < StandardError
|
|
43
47
|
attr_reader :code, :retryable
|
|
44
48
|
|
|
@@ -54,14 +58,30 @@ module LogBrew
|
|
|
54
58
|
end
|
|
55
59
|
|
|
56
60
|
class TransportResponse
|
|
57
|
-
attr_reader :status_code, :attempts
|
|
61
|
+
attr_reader :status_code, :attempts, :batches
|
|
58
62
|
|
|
59
|
-
def initialize(status_code, attempts)
|
|
63
|
+
def initialize(status_code, attempts, batches = 1)
|
|
60
64
|
@status_code = status_code
|
|
61
65
|
@attempts = attempts
|
|
66
|
+
@batches = batches
|
|
62
67
|
end
|
|
63
68
|
end
|
|
64
69
|
|
|
70
|
+
class DeliveryFailure < SdkError
|
|
71
|
+
attr_reader :automatic_pause_reason
|
|
72
|
+
|
|
73
|
+
def initialize(code, message, automatic_retryable: false, automatic_pause_reason: nil)
|
|
74
|
+
@automatic_retryable = automatic_retryable
|
|
75
|
+
@automatic_pause_reason = automatic_pause_reason
|
|
76
|
+
super(code, message)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def automatic_retryable?
|
|
80
|
+
@automatic_retryable
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
private_constant :DeliveryFailure
|
|
84
|
+
|
|
65
85
|
class RecordingTransport
|
|
66
86
|
attr_reader :sent_bodies
|
|
67
87
|
|
|
@@ -92,7 +112,7 @@ module LogBrew
|
|
|
92
112
|
end
|
|
93
113
|
|
|
94
114
|
class HttpTransport
|
|
95
|
-
DEFAULT_ENDPOINT = "https://api.logbrew.
|
|
115
|
+
DEFAULT_ENDPOINT = "https://api.logbrew.co/v1/events"
|
|
96
116
|
DEFAULT_TIMEOUT = 10
|
|
97
117
|
|
|
98
118
|
attr_reader :endpoint, :headers, :timeout, :http_client
|
|
@@ -114,7 +134,9 @@ module LogBrew
|
|
|
114
134
|
@headers.each { |name, value| request[name] = value }
|
|
115
135
|
request.body = body
|
|
116
136
|
|
|
117
|
-
response =
|
|
137
|
+
response = HttpClientTracing.suppress do
|
|
138
|
+
@http_client ? @http_client.request(request) : request_with_default_client(request)
|
|
139
|
+
end
|
|
118
140
|
TransportResponse.new(response.code.to_i, 1)
|
|
119
141
|
rescue TransportError
|
|
120
142
|
raise
|
|
@@ -668,7 +690,7 @@ module LogBrew
|
|
|
668
690
|
module_function
|
|
669
691
|
|
|
670
692
|
def require_non_empty(label, value)
|
|
671
|
-
return if value.is_a?(String) && !value.strip.empty?
|
|
693
|
+
return if value.is_a?(String) && value.valid_encoding? && !value.strip.empty?
|
|
672
694
|
|
|
673
695
|
raise SdkError.new("validation_error", "#{label} must be non-empty")
|
|
674
696
|
end
|
|
@@ -739,7 +761,23 @@ module LogBrew
|
|
|
739
761
|
end
|
|
740
762
|
|
|
741
763
|
class Client
|
|
742
|
-
|
|
764
|
+
DEFAULT_AUTOMATIC_FLUSH_INTERVAL = 5.0
|
|
765
|
+
DEFAULT_AUTOMATIC_FLUSH_THRESHOLD = 100
|
|
766
|
+
DEFAULT_AUTOMATIC_RETRY_BASE_DELAY = 0.25
|
|
767
|
+
DEFAULT_AUTOMATIC_RETRY_MAX_DELAY = 30.0
|
|
768
|
+
|
|
769
|
+
def self.create(
|
|
770
|
+
api_key:,
|
|
771
|
+
sdk_name:,
|
|
772
|
+
sdk_version:,
|
|
773
|
+
max_retries: 2,
|
|
774
|
+
max_queue_size: BoundedEventQueue::DEFAULT_MAX_SIZE,
|
|
775
|
+
max_queue_bytes: BoundedEventQueue::DEFAULT_MAX_BYTES,
|
|
776
|
+
on_event_dropped: nil,
|
|
777
|
+
max_batch_size: EventBatcher::DEFAULT_MAX_SIZE,
|
|
778
|
+
max_batch_bytes: EventBatcher::DEFAULT_MAX_BYTES,
|
|
779
|
+
persistent_queue_path: nil
|
|
780
|
+
)
|
|
743
781
|
Validation.require_non_empty("api_key", api_key)
|
|
744
782
|
Validation.require_non_empty("sdk_name", sdk_name)
|
|
745
783
|
Validation.require_non_empty("sdk_version", sdk_version)
|
|
@@ -747,25 +785,158 @@ module LogBrew
|
|
|
747
785
|
|
|
748
786
|
new(
|
|
749
787
|
api_key: api_key,
|
|
750
|
-
sdk: {
|
|
751
|
-
|
|
788
|
+
sdk: {
|
|
789
|
+
"name" => sdk_name.dup.freeze,
|
|
790
|
+
"language" => "ruby",
|
|
791
|
+
"version" => sdk_version.dup.freeze
|
|
792
|
+
}.freeze,
|
|
793
|
+
max_retries: max_retries,
|
|
794
|
+
max_queue_size: max_queue_size,
|
|
795
|
+
max_queue_bytes: max_queue_bytes,
|
|
796
|
+
on_event_dropped: on_event_dropped,
|
|
797
|
+
max_batch_size: max_batch_size,
|
|
798
|
+
max_batch_bytes: max_batch_bytes,
|
|
799
|
+
persistent_queue_path: persistent_queue_path
|
|
752
800
|
)
|
|
753
801
|
end
|
|
754
802
|
|
|
755
|
-
def
|
|
803
|
+
def self.create_automatic(
|
|
804
|
+
api_key:,
|
|
805
|
+
sdk_name:,
|
|
806
|
+
sdk_version:,
|
|
807
|
+
transport:,
|
|
808
|
+
flush_interval: DEFAULT_AUTOMATIC_FLUSH_INTERVAL,
|
|
809
|
+
flush_threshold: DEFAULT_AUTOMATIC_FLUSH_THRESHOLD,
|
|
810
|
+
retry_base_delay: DEFAULT_AUTOMATIC_RETRY_BASE_DELAY,
|
|
811
|
+
retry_max_delay: DEFAULT_AUTOMATIC_RETRY_MAX_DELAY,
|
|
812
|
+
**options
|
|
813
|
+
)
|
|
814
|
+
max_queue_size = options.fetch(:max_queue_size, BoundedEventQueue::DEFAULT_MAX_SIZE)
|
|
815
|
+
AutomaticDelivery.validate!(
|
|
816
|
+
transport: transport,
|
|
817
|
+
flush_interval: flush_interval,
|
|
818
|
+
flush_threshold: flush_threshold,
|
|
819
|
+
retry_base_delay: retry_base_delay,
|
|
820
|
+
retry_max_delay: retry_max_delay,
|
|
821
|
+
max_queue_size: max_queue_size
|
|
822
|
+
)
|
|
823
|
+
client = create(
|
|
824
|
+
api_key: api_key,
|
|
825
|
+
sdk_name: sdk_name,
|
|
826
|
+
sdk_version: sdk_version,
|
|
827
|
+
**options
|
|
828
|
+
)
|
|
829
|
+
client.send(
|
|
830
|
+
:enable_automatic_delivery,
|
|
831
|
+
transport: transport,
|
|
832
|
+
flush_interval: flush_interval,
|
|
833
|
+
flush_threshold: flush_threshold,
|
|
834
|
+
retry_base_delay: retry_base_delay,
|
|
835
|
+
retry_max_delay: retry_max_delay
|
|
836
|
+
)
|
|
837
|
+
client
|
|
838
|
+
end
|
|
839
|
+
|
|
840
|
+
def initialize(
|
|
841
|
+
api_key:,
|
|
842
|
+
sdk:,
|
|
843
|
+
max_retries:,
|
|
844
|
+
max_queue_size: BoundedEventQueue::DEFAULT_MAX_SIZE,
|
|
845
|
+
max_queue_bytes: BoundedEventQueue::DEFAULT_MAX_BYTES,
|
|
846
|
+
on_event_dropped: nil,
|
|
847
|
+
max_batch_size: EventBatcher::DEFAULT_MAX_SIZE,
|
|
848
|
+
max_batch_bytes: EventBatcher::DEFAULT_MAX_BYTES,
|
|
849
|
+
persistent_queue_path: nil
|
|
850
|
+
)
|
|
756
851
|
@api_key = api_key
|
|
757
852
|
@sdk = sdk
|
|
758
853
|
@max_retries = max_retries
|
|
759
|
-
@
|
|
854
|
+
@event_batcher = EventBatcher.new(sdk: sdk, max_size: max_batch_size, max_bytes: max_batch_bytes)
|
|
855
|
+
event_store = nil
|
|
856
|
+
begin
|
|
857
|
+
event_store = PersistentEventStore.open(path: persistent_queue_path) unless persistent_queue_path.nil?
|
|
858
|
+
@event_queue = BoundedEventQueue.new(
|
|
859
|
+
max_size: max_queue_size,
|
|
860
|
+
max_bytes: max_queue_bytes,
|
|
861
|
+
max_event_bytes: @event_batcher.max_event_bytes,
|
|
862
|
+
on_event_dropped: on_event_dropped,
|
|
863
|
+
event_store: event_store
|
|
864
|
+
)
|
|
865
|
+
rescue StandardError
|
|
866
|
+
event_store&.close
|
|
867
|
+
raise
|
|
868
|
+
end
|
|
869
|
+
@state_mutex = Mutex.new
|
|
870
|
+
@flush_mutex = Mutex.new
|
|
760
871
|
@closed = false
|
|
872
|
+
@closing = false
|
|
873
|
+
@retry_batch = nil
|
|
874
|
+
@automatic_delivery = nil
|
|
761
875
|
end
|
|
762
876
|
|
|
763
877
|
def pending_events
|
|
764
|
-
@
|
|
878
|
+
@event_queue.length
|
|
879
|
+
end
|
|
880
|
+
|
|
881
|
+
def pending_event_bytes
|
|
882
|
+
@event_queue.pending_bytes
|
|
883
|
+
end
|
|
884
|
+
|
|
885
|
+
def dropped_events
|
|
886
|
+
@event_queue.dropped_events
|
|
887
|
+
end
|
|
888
|
+
|
|
889
|
+
def delivery_health
|
|
890
|
+
controller = @automatic_delivery
|
|
891
|
+
unless controller.nil?
|
|
892
|
+
controller.assert_process_ownership!
|
|
893
|
+
return controller.snapshot
|
|
894
|
+
end
|
|
895
|
+
|
|
896
|
+
metrics = queue_metrics
|
|
897
|
+
state = @state_mutex.synchronize { @closed ? "closed" : "manual" }
|
|
898
|
+
DeliveryHealth.new(
|
|
899
|
+
state: state,
|
|
900
|
+
queued_events: metrics.fetch(:queued_events),
|
|
901
|
+
queued_bytes: metrics.fetch(:queued_bytes),
|
|
902
|
+
dropped_events: metrics.fetch(:dropped_events),
|
|
903
|
+
in_flight: false,
|
|
904
|
+
last_outcome: "none",
|
|
905
|
+
consecutive_failures: 0,
|
|
906
|
+
pause_reason: nil,
|
|
907
|
+
successful_flushes: 0,
|
|
908
|
+
failed_flushes: 0,
|
|
909
|
+
retry_delay_ms: 0
|
|
910
|
+
)
|
|
911
|
+
end
|
|
912
|
+
|
|
913
|
+
def recover_automatic_delivery
|
|
914
|
+
controller = require_automatic_delivery
|
|
915
|
+
controller.assert_process_ownership!
|
|
916
|
+
flush
|
|
917
|
+
end
|
|
918
|
+
|
|
919
|
+
def stop_automatic_delivery
|
|
920
|
+
controller = require_automatic_delivery
|
|
921
|
+
controller.stop
|
|
765
922
|
end
|
|
766
923
|
|
|
767
924
|
def preview_json
|
|
768
|
-
|
|
925
|
+
snapshot = @event_queue.snapshot
|
|
926
|
+
JSON.pretty_generate("sdk" => @sdk, "events" => snapshot.events)
|
|
927
|
+
end
|
|
928
|
+
|
|
929
|
+
def purge_pending_events
|
|
930
|
+
ensure_not_reentrant_flush
|
|
931
|
+
controller = @automatic_delivery
|
|
932
|
+
controller&.assert_process_ownership!
|
|
933
|
+
@flush_mutex.synchronize do
|
|
934
|
+
@state_mutex.synchronize { ensure_open }
|
|
935
|
+
@retry_batch = nil
|
|
936
|
+
removed = @event_queue.purge
|
|
937
|
+
controller&.queue_changed
|
|
938
|
+
removed
|
|
939
|
+
end
|
|
769
940
|
end
|
|
770
941
|
|
|
771
942
|
def release(id, timestamp, attributes)
|
|
@@ -796,59 +967,208 @@ module LogBrew
|
|
|
796
967
|
push_event("action", id, timestamp, validate_action(attributes))
|
|
797
968
|
end
|
|
798
969
|
|
|
799
|
-
def flush(transport)
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
970
|
+
def flush(transport = nil)
|
|
971
|
+
ensure_not_reentrant_flush
|
|
972
|
+
controller = @automatic_delivery
|
|
973
|
+
controller&.assert_process_ownership!
|
|
974
|
+
resolved_transport = resolve_transport(transport)
|
|
975
|
+
@flush_mutex.synchronize do
|
|
976
|
+
@state_mutex.synchronize { ensure_open }
|
|
977
|
+
previous = controller&.begin_explicit_flush
|
|
978
|
+
begin
|
|
979
|
+
response = flush_internal(resolved_transport)
|
|
980
|
+
controller&.complete_explicit_flush(previous, response)
|
|
981
|
+
response
|
|
982
|
+
rescue StandardError => error
|
|
983
|
+
controller&.fail_explicit_flush(previous, error)
|
|
984
|
+
raise
|
|
985
|
+
end
|
|
986
|
+
end
|
|
803
987
|
end
|
|
804
988
|
|
|
805
|
-
def shutdown(transport)
|
|
806
|
-
|
|
989
|
+
def shutdown(transport = nil)
|
|
990
|
+
ensure_not_reentrant_flush
|
|
991
|
+
controller = @automatic_delivery
|
|
992
|
+
controller&.assert_process_ownership!
|
|
993
|
+
resolved_transport = resolve_transport(transport)
|
|
994
|
+
controller&.prepare_shutdown
|
|
995
|
+
@flush_mutex.synchronize do
|
|
996
|
+
@state_mutex.synchronize do
|
|
997
|
+
ensure_open
|
|
998
|
+
@closing = true
|
|
999
|
+
end
|
|
807
1000
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
1001
|
+
succeeded = false
|
|
1002
|
+
failure = nil
|
|
1003
|
+
begin
|
|
1004
|
+
response = flush_internal(resolved_transport)
|
|
1005
|
+
@event_queue.close
|
|
1006
|
+
succeeded = true
|
|
1007
|
+
controller&.complete_shutdown(response)
|
|
1008
|
+
response
|
|
1009
|
+
rescue StandardError => error
|
|
1010
|
+
failure = error
|
|
1011
|
+
raise
|
|
1012
|
+
ensure
|
|
1013
|
+
@state_mutex.synchronize do
|
|
1014
|
+
@closed = true if succeeded
|
|
1015
|
+
@closing = false
|
|
1016
|
+
end
|
|
1017
|
+
controller&.resume_after_failed_shutdown(failure) unless succeeded
|
|
1018
|
+
end
|
|
1019
|
+
end
|
|
811
1020
|
end
|
|
812
1021
|
|
|
813
1022
|
private
|
|
814
1023
|
|
|
815
1024
|
def push_event(type, id, timestamp, attributes)
|
|
816
|
-
|
|
817
|
-
|
|
1025
|
+
@automatic_delivery&.assert_process_ownership!
|
|
818
1026
|
Validation.require_non_empty("event id", id)
|
|
819
1027
|
Validation.require_timestamp(timestamp)
|
|
820
|
-
|
|
1028
|
+
event = {
|
|
821
1029
|
"type" => type,
|
|
822
1030
|
"timestamp" => timestamp,
|
|
823
1031
|
"id" => id,
|
|
824
1032
|
"attributes" => attributes
|
|
825
1033
|
}
|
|
1034
|
+
notice = @state_mutex.synchronize do
|
|
1035
|
+
ensure_open
|
|
1036
|
+
@event_queue.enqueue(event_id: id, event_type: type, event: event)
|
|
1037
|
+
end
|
|
1038
|
+
@event_queue.notify_drop(notice)
|
|
1039
|
+
@automatic_delivery&.notify_capture if notice.nil?
|
|
1040
|
+
end
|
|
1041
|
+
|
|
1042
|
+
def enable_automatic_delivery(transport:, flush_interval:, flush_threshold:, retry_base_delay:, retry_max_delay:)
|
|
1043
|
+
@automatic_delivery = AutomaticDelivery.new(
|
|
1044
|
+
client: self,
|
|
1045
|
+
transport: transport,
|
|
1046
|
+
flush_interval: flush_interval,
|
|
1047
|
+
flush_threshold: flush_threshold,
|
|
1048
|
+
retry_base_delay: retry_base_delay,
|
|
1049
|
+
retry_max_delay: retry_max_delay
|
|
1050
|
+
)
|
|
1051
|
+
end
|
|
1052
|
+
|
|
1053
|
+
def perform_automatic_flush(controller, generation, transport)
|
|
1054
|
+
@flush_mutex.synchronize do
|
|
1055
|
+
begin
|
|
1056
|
+
@state_mutex.synchronize { ensure_open }
|
|
1057
|
+
response = flush_internal(transport)
|
|
1058
|
+
controller.complete_automatic_flush(generation, response)
|
|
1059
|
+
rescue StandardError => error
|
|
1060
|
+
controller.fail_automatic_flush(generation, error)
|
|
1061
|
+
end
|
|
1062
|
+
end
|
|
826
1063
|
end
|
|
827
1064
|
|
|
828
1065
|
def flush_internal(transport)
|
|
829
|
-
|
|
1066
|
+
snapshot = @event_queue.snapshot
|
|
1067
|
+
return TransportResponse.new(204, 0, 0) if snapshot.event_count.zero?
|
|
1068
|
+
|
|
1069
|
+
remaining_events = snapshot.event_count
|
|
1070
|
+
attempts = 0
|
|
1071
|
+
batches = 0
|
|
1072
|
+
status_code = 204
|
|
1073
|
+
|
|
1074
|
+
while remaining_events.positive?
|
|
1075
|
+
batch = @retry_batch || @event_batcher.next_batch(
|
|
1076
|
+
@event_queue.snapshot.serialized_events,
|
|
1077
|
+
limit: remaining_events
|
|
1078
|
+
)
|
|
1079
|
+
@retry_batch = batch
|
|
1080
|
+
response = send_batch(transport, batch.body)
|
|
1081
|
+
compaction_error = @event_queue.acknowledge(batch)
|
|
1082
|
+
@retry_batch = nil
|
|
1083
|
+
raise compaction_error unless compaction_error.nil?
|
|
1084
|
+
remaining_events -= batch.event_count
|
|
1085
|
+
attempts += response.attempts
|
|
1086
|
+
batches += 1
|
|
1087
|
+
status_code = response.status_code
|
|
1088
|
+
end
|
|
830
1089
|
|
|
831
|
-
|
|
1090
|
+
TransportResponse.new(status_code, attempts, batches)
|
|
1091
|
+
end
|
|
1092
|
+
|
|
1093
|
+
def send_batch(transport, body)
|
|
832
1094
|
max_attempts = @max_retries + 1
|
|
833
1095
|
(1..max_attempts).each do |attempt|
|
|
834
1096
|
begin
|
|
835
1097
|
response = transport.send(@api_key, body)
|
|
836
|
-
|
|
1098
|
+
if response.status_code == 401 || response.status_code == 403
|
|
1099
|
+
raise DeliveryFailure.new(
|
|
1100
|
+
"unauthenticated",
|
|
1101
|
+
"transport rejected the API key",
|
|
1102
|
+
automatic_pause_reason: "authentication"
|
|
1103
|
+
)
|
|
1104
|
+
end
|
|
837
1105
|
|
|
838
1106
|
if response.status_code >= 200 && response.status_code < 300
|
|
839
|
-
@events.clear
|
|
840
1107
|
return TransportResponse.new(response.status_code, attempt)
|
|
841
1108
|
end
|
|
842
|
-
|
|
1109
|
+
retryable_status = response.status_code == 408 || response.status_code >= 500
|
|
1110
|
+
next if retryable_status && attempt < max_attempts
|
|
1111
|
+
|
|
1112
|
+
if retryable_status
|
|
1113
|
+
raise DeliveryFailure.new(
|
|
1114
|
+
"transport_error",
|
|
1115
|
+
"unexpected transport status #{response.status_code}",
|
|
1116
|
+
automatic_retryable: true
|
|
1117
|
+
)
|
|
1118
|
+
end
|
|
1119
|
+
|
|
1120
|
+
pause_reason = case response.status_code
|
|
1121
|
+
when 429 then "quota"
|
|
1122
|
+
when 400, 422 then "validation"
|
|
1123
|
+
else "nonretryable"
|
|
1124
|
+
end
|
|
843
1125
|
|
|
844
|
-
raise
|
|
1126
|
+
raise DeliveryFailure.new(
|
|
1127
|
+
"transport_error",
|
|
1128
|
+
"unexpected transport status #{response.status_code}",
|
|
1129
|
+
automatic_pause_reason: pause_reason
|
|
1130
|
+
)
|
|
845
1131
|
rescue TransportError => error
|
|
846
1132
|
next if error.retryable && attempt < max_attempts
|
|
847
1133
|
|
|
848
|
-
raise
|
|
1134
|
+
raise DeliveryFailure.new(
|
|
1135
|
+
error.code,
|
|
1136
|
+
error.message,
|
|
1137
|
+
automatic_retryable: error.retryable
|
|
1138
|
+
)
|
|
849
1139
|
end
|
|
850
1140
|
end
|
|
851
|
-
raise
|
|
1141
|
+
raise DeliveryFailure.new("transport_error", "exhausted retries", automatic_retryable: true)
|
|
1142
|
+
end
|
|
1143
|
+
|
|
1144
|
+
def require_automatic_delivery
|
|
1145
|
+
return @automatic_delivery unless @automatic_delivery.nil?
|
|
1146
|
+
|
|
1147
|
+
raise SdkError.new("automatic_delivery_error", "client does not own automatic delivery")
|
|
1148
|
+
end
|
|
1149
|
+
|
|
1150
|
+
def resolve_transport(transport)
|
|
1151
|
+
resolved = transport || @automatic_delivery&.transport
|
|
1152
|
+
unless resolved.respond_to?(:send)
|
|
1153
|
+
raise SdkError.new("validation_error", "transport must respond to send")
|
|
1154
|
+
end
|
|
1155
|
+
|
|
1156
|
+
resolved
|
|
1157
|
+
end
|
|
1158
|
+
|
|
1159
|
+
def queue_metrics
|
|
1160
|
+
@event_queue.metrics
|
|
1161
|
+
end
|
|
1162
|
+
|
|
1163
|
+
def ensure_not_reentrant_flush
|
|
1164
|
+
return unless @flush_mutex.owned?
|
|
1165
|
+
|
|
1166
|
+
raise SdkError.new("flush_error", "flush is already in progress")
|
|
1167
|
+
end
|
|
1168
|
+
|
|
1169
|
+
def ensure_open
|
|
1170
|
+
raise SdkError.new("shutdown_error", "client is already shut down") if @closed
|
|
1171
|
+
raise SdkError.new("shutdown_error", "client is shutting down") if @closing
|
|
852
1172
|
end
|
|
853
1173
|
|
|
854
1174
|
def validate_release(attributes)
|
|
@@ -937,6 +1257,7 @@ module LogBrew
|
|
|
937
1257
|
if !duration_ms.nil? && (!duration_ms.is_a?(Numeric) || duration_ms.negative?)
|
|
938
1258
|
raise SdkError.new("validation_error", "span durationMs must be non-negative")
|
|
939
1259
|
end
|
|
1260
|
+
span_events = SpanEvents.validate(Validation.read(attributes, "events"))
|
|
940
1261
|
|
|
941
1262
|
with_metadata(
|
|
942
1263
|
{
|
|
@@ -947,6 +1268,7 @@ module LogBrew
|
|
|
947
1268
|
payload["parentSpanId"] = parent_span_id unless parent_span_id.nil?
|
|
948
1269
|
payload["status"] = status
|
|
949
1270
|
payload["durationMs"] = duration_ms unless duration_ms.nil?
|
|
1271
|
+
payload["events"] = span_events unless span_events.nil?
|
|
950
1272
|
end,
|
|
951
1273
|
attributes
|
|
952
1274
|
)
|
|
@@ -992,8 +1314,10 @@ module LogBrew
|
|
|
992
1314
|
payload
|
|
993
1315
|
end
|
|
994
1316
|
end
|
|
1317
|
+
|
|
1318
|
+
require_relative "logbrew/automatic_delivery"
|
|
995
1319
|
end
|
|
996
1320
|
|
|
997
|
-
%w[product_timeline traceparent trace operation_tracing support_ticket].each do |path|
|
|
1321
|
+
%w[product_timeline traceparent trace span_events operation_tracing http_client_tracing support_ticket worker_lifecycle].each do |path|
|
|
998
1322
|
require_relative "logbrew/#{path}"
|
|
999
1323
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: logbrew-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- LogBrew
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Public LogBrew Ruby SDK for building, validating, and flushing event
|
|
14
14
|
batches.
|
|
@@ -19,16 +19,28 @@ extra_rdoc_files: []
|
|
|
19
19
|
files:
|
|
20
20
|
- README.md
|
|
21
21
|
- examples/Makefile
|
|
22
|
+
- examples/automatic_delivery.rb
|
|
22
23
|
- examples/first_useful_telemetry.rb
|
|
23
24
|
- examples/http_trace_correlation.rb
|
|
25
|
+
- examples/persistent_worker_delivery.rb
|
|
24
26
|
- examples/readme_example.rb
|
|
25
27
|
- examples/real_user_smoke.rb
|
|
28
|
+
- examples/sidekiq_tracing.rb
|
|
26
29
|
- lib/logbrew.rb
|
|
30
|
+
- lib/logbrew/automatic_delivery.rb
|
|
31
|
+
- lib/logbrew/bounded_event_queue.rb
|
|
32
|
+
- lib/logbrew/event_batcher.rb
|
|
33
|
+
- lib/logbrew/faraday_tracing.rb
|
|
34
|
+
- lib/logbrew/http_client_tracing.rb
|
|
27
35
|
- lib/logbrew/operation_tracing.rb
|
|
36
|
+
- lib/logbrew/persistent_event_store.rb
|
|
28
37
|
- lib/logbrew/product_timeline.rb
|
|
38
|
+
- lib/logbrew/sidekiq.rb
|
|
39
|
+
- lib/logbrew/span_events.rb
|
|
29
40
|
- lib/logbrew/support_ticket.rb
|
|
30
41
|
- lib/logbrew/trace.rb
|
|
31
42
|
- lib/logbrew/traceparent.rb
|
|
43
|
+
- lib/logbrew/worker_lifecycle.rb
|
|
32
44
|
homepage: https://github.com/LogBrewCo/sdk
|
|
33
45
|
licenses:
|
|
34
46
|
- MIT
|