langda 0.1.0 → 0.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b45008f13ae56ee2ae505fdf1fc551ad57bcbbf276187ee0c18514a7df713b23
4
- data.tar.gz: ea8d92f235f9ade134def26368a35b1bf559f161162b36dd583b5470e59e3837
3
+ metadata.gz: 32128642b12f722a798887e1be70fd8820af2b498b4bb29848a27104a8002e9f
4
+ data.tar.gz: de5538f68d89d71fd05bd013afa126863755d11bdcd9d0ce302c5e61c1e01a92
5
5
  SHA512:
6
- metadata.gz: a9b7e62d8337a902777f55b181a94032169f9fc82b16d1e594cc78d31f5403fb4233bf67fd4a9aa1a15ac98be3d22c506546dafc61f72ba840517fedf85ebf4e
7
- data.tar.gz: 4af4462b9dc36fb19d83f88185eb9e7379fc6b364ce65b1eaf200894b1bf5b51e5da54810100a0fc51f3b218391d2fe66223692a3f4ebab6e798f02824a3bd12
6
+ metadata.gz: 36899fb283dc7ff1411c25b61c2e8e16abc52252ccd11a9281e5d329bc64ddee19e70239d21ea56a21b600cb97aea63397355a9ac8b0e9a517dce90347752b7f
7
+ data.tar.gz: e485b13e58ced52fe06e13255f8cfe0f069ee0446984f12333af914c752a6155bbdcc76039b112e1c9a7746160caa8acc5c6b5b8f7833f94c5d58e4ce51046f6
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Langda
1
+ # Langda • Preferred Development Environment
2
2
 
3
3
  **Langda** is a lightweight Ruby performance helper that automatically detects loops,
4
4
  counts their iterations, measures execution time, and logs slow loops — all without
@@ -1,105 +1,160 @@
1
- module Langda
2
- module LoopPatch
3
-
4
- #########################################################
5
- # UNIVERSAL LOOP WRAPPER (SAFE)
6
- #########################################################
7
- def _safe_loop(kind, args, block)
8
- # No block → run original method
9
- return yield unless Langda.enabled? && block
10
-
11
- start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
12
- count = 0
13
-
14
- begin
15
- inner = proc do |*yield_args|
16
- count += 1
17
- block.call(*yield_args)
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+
5
+ module IterationLogger
6
+ VERSION = "0.1.0"
7
+
8
+ ITERATION_METHODS = [
9
+ :each, :map, :collect, :select, :find_all, :reject, :grep, :grep_v,
10
+ :each_with_index, :flat_map, :inject, :reduce, :partition, :find,
11
+ :any?, :all?, :none?, :times
12
+ ].to_set.freeze
13
+
14
+ class << self
15
+ def logger
16
+ @logger ||= begin
17
+ if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
18
+ Rails.logger
19
+ else
20
+ std_logger = ::Logger.new($stdout)
21
+ std_logger.level = ::Logger::INFO
22
+ std_logger.progname = "Langda Logger"
23
+ std_logger
18
24
  end
19
-
20
- result = yield(inner)
21
-
22
- _langda_log(kind, count, start)
23
- result
24
-
25
- rescue => e
26
- Langda::Log.warn("Langda fallback for #{kind}: #{e.class} #{e.message}")
27
- yield(block) # fallback super
28
25
  end
29
26
  end
30
27
 
28
+ # Enable the TracePoint listener (idempotent)
29
+ def enable!
30
+ return if enabled?
31
31
 
32
- #########################################################
33
- # ARRAY / ENUMERABLE LOOP METHODS
34
- #########################################################
32
+ @tracepoint = TracePoint.new(:call, :c_call) do |tp|
33
+ begin
34
+ handle_tracepoint(tp)
35
+ rescue => e
36
+ # Avoid raising inside TracePoint (would be fatal)
37
+ logger.error("Langda Logger internal error: #{e.class}: #{e.message}\n#{e.backtrace&.first(5)&.join("\n")}")
38
+ end
39
+ end
35
40
 
36
- def each(*args, &block)
37
- _safe_loop(:each, args, block) { |inner| super(*args, &inner) }
41
+ @tracepoint.enable
42
+ @enabled = true
43
+ logger.info("Langda Logger enabled (version #{VERSION})")
38
44
  end
39
45
 
40
- def map(*args, &block)
41
- _safe_loop(:map, args, block) { |inner| super(*args, &inner) }
46
+ def disable!
47
+ return unless enabled?
48
+ @tracepoint&.disable
49
+ @tracepoint = nil
50
+ @enabled = false
51
+ logger.info("Langda Logger disabled")
42
52
  end
43
53
 
44
- def select(*args, &block)
45
- _safe_loop(:select, args, block) { |inner| super(*args, &inner) }
54
+ def enabled?
55
+ !!@enabled
46
56
  end
47
57
 
48
- def reject(*args, &block)
49
- _safe_loop(:reject, args, block) { |inner| super(*args, &inner) }
50
- end
58
+ private
51
59
 
52
- def each_with_index(*args, &block)
53
- _safe_loop(:each_with_index, args, block) { |inner| super(*args, &inner) }
54
- end
60
+ # Decide whether to log and what to log
61
+ def handle_tracepoint(tp)
62
+ method_sym = tp.method_id
63
+ return unless method_sym && ITERATION_METHODS.include?(method_sym)
55
64
 
56
- def each_with_object(obj, &block)
57
- _safe_loop(:each_with_object, [obj], block) { |inner| super(obj, &inner) }
58
- end
65
+ path = tp.path
66
+ lineno = tp.lineno
59
67
 
68
+ # We only log when the call site is inside /app/
69
+ return unless path && path.include?("/app/")
60
70
 
61
- #########################################################
62
- # INTEGER LOOP METHODS
63
- #########################################################
71
+ receiver = safe_receiver(tp)
72
+ class_name = receiver_class_name(receiver, tp)
64
73
 
65
- def times(&block)
66
- _safe_loop(:times, [], block) { |inner| super(&inner) }
67
- end
74
+ method_name = method_sym.to_s
68
75
 
69
- def upto(limit, &block)
70
- _safe_loop(:upto, [limit], block) { |inner| super(limit, &inner) }
76
+ # Try to determine "current count" - number of elements to be iterated
77
+ count = determine_count(receiver, method_sym)
78
+
79
+ message = {
80
+ file: path,
81
+ line: lineno,
82
+ class: class_name,
83
+ method: method_name,
84
+ count: count
85
+ }
86
+
87
+ # Use Rails.logger if available, otherwise STDOUT via our logger
88
+ logger.info("Langda Logger: #{message}")
71
89
  end
72
90
 
73
- def downto(limit, &block)
74
- _safe_loop(:downto, [limit], block) { |inner| super(limit, &inner) }
91
+ # Safely fetch the receiver (tp.self can sometimes be nil/corrupt in odd cases; rescue)
92
+ def safe_receiver(tp)
93
+ tp.self
94
+ rescue => _
95
+ nil
75
96
  end
76
97
 
77
- def step(limit = nil, step = nil, &block)
78
- _safe_loop(:step, [limit, step], block) do |inner|
79
- super(limit, step, &inner)
98
+ def receiver_class_name(receiver, tp)
99
+ if receiver
100
+ # Prefer actual receiver class name
101
+ receiver.class.name rescue tp.defined_class.to_s
102
+ else
103
+ # Fallback to defined_class or '?'
104
+ (tp.defined_class && tp.defined_class.to_s) || "Unknown"
80
105
  end
81
106
  end
82
107
 
108
+ # Best-effort to determine count:
109
+ # - For Integer#times, the receiver (an Integer) itself is the count
110
+ # - Prefer length, size, then count method if available, but call in safe rescue
111
+ # - For ActiveRecord::Relation, size may perform DB work; user should be aware
112
+ def determine_count(receiver, method_sym)
113
+ # Integer#times => receiver is an Integer
114
+ if method_sym == :times && receiver.is_a?(Integer)
115
+ return receiver
116
+ end
83
117
 
84
- #########################################################
85
- # LOGGER
86
- #########################################################
87
- def _langda_log(kind, count, start)
88
- ms = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000
89
- return unless ms > Langda.threshold_ms
118
+ return nil unless receiver
119
+
120
+ # Try methods in an order that is best-effort and less likely to cause heavy DB ops
121
+ [:length, :size, :count].each do |m|
122
+ if receiver.respond_to?(m)
123
+ begin
124
+ val = receiver.public_send(m)
125
+ # Ensure it's numeric
126
+ return val if val.is_a?(Integer)
127
+ rescue => _
128
+ # swallow - sometimes ActiveRecord proxies may raise when calling count/size
129
+ next
130
+ end
131
+ end
132
+ end
90
133
 
91
- Langda::Log.warn("#{kind} → #{count} iterations → #{ms.round(3)} ms")
134
+ nil
92
135
  end
93
-
94
136
  end
95
- end
96
-
97
137
 
98
- #########################################################
99
- # APPLY PATCHES
100
- #########################################################
138
+ # Railtie to auto-start in Rails apps
139
+ if defined?(Rails)
140
+ require "rails/railtie"
141
+
142
+ class Railtie < Rails::Railtie
143
+ initializer "iteration_logger.configure_rails_initialization" do
144
+ # Enable after Rails finishes initializing to ensure Rails.logger is available
145
+ ActiveSupport.on_load(:after_initialize) do
146
+ begin
147
+ IterationLogger.enable!
148
+ rescue => e
149
+ IterationLogger.logger.error("Langda Logger failed to enable: #{e.class}: #{e.message}")
150
+ end
151
+ end
152
+ end
101
153
 
102
- Array.prepend(Langda::LoopPatch)
103
- Hash.prepend(Langda::LoopPatch)
104
- Enumerable.prepend(Langda::LoopPatch)
105
- Integer.prepend(Langda::LoopPatch)
154
+ # Allow config flag to disable if apps wish to
155
+ rake_tasks do
156
+ # no rake tasks; placeholder so Rails doesn't warn about railtie with no hooks
157
+ end
158
+ end
159
+ end
160
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Langda
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
data/lib/langda.rb CHANGED
@@ -1,4 +1,3 @@
1
- require "langda/logger"
2
1
 
3
2
  module Langda
4
3
  class << self
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: langda
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - mrmalvi
@@ -42,7 +42,6 @@ files:
42
42
  - bin/console
43
43
  - bin/setup
44
44
  - lib/langda.rb
45
- - lib/langda/logger.rb
46
45
  - lib/langda/patches.rb
47
46
  - lib/langda/version.rb
48
47
  - sig/langda.rbs
data/lib/langda/logger.rb DELETED
@@ -1,20 +0,0 @@
1
- module Langda
2
- module Log
3
- APP_ROOT = Rails.root.to_s
4
- def self.warn(msg)
5
-
6
- loc = caller.find { |c| c.start_with?(APP_ROOT) }
7
-
8
- loc = caller.find do |c|
9
- c.include?("#{APP_ROOT}/app/")
10
- end
11
- return unless loc
12
- file = loc.split(":")
13
- if defined?(Rails) && Rails.logger
14
- super("[Langda] #{msg} at #{file}")
15
- else
16
- puts ("[Langda] #{msg} at #{file}")
17
- end
18
- end
19
- end
20
- end