prompt_warden 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: 4e6eca8fc76a1284d35dd490ffc8285ed055fad9c799f3530e5165d085234dda
4
- data.tar.gz: 8cebcab3d6b2fbca74c20ef8659a72d3f3f3e3caa5a05faf349c3c00cb1fa702
3
+ metadata.gz: 664b869ad3c4946de2726eb00ba6530e4eabf8e5f77232275bc3af91bd265199
4
+ data.tar.gz: b56c3c7e33b6478ea8b9021c348c185e2dd410c7860c89309216a17959c15ecc
5
5
  SHA512:
6
- metadata.gz: 0a6616bab2c115f45597e175e28af8ed02c18b83ad31587263b369395f52747ced29af3b193fb59f76dc40643b20d6b1bb06eb49be354ef514a646d41edf38ca
7
- data.tar.gz: 2ac9bbb2c41a91cb0b3b8b13c37d48696b29b9db4708cdfc1924024d3b710a5acd0e9cf53cd1f7c93b959489ffafa9dbe941e91538c75d756a94e90a5cd40527
6
+ metadata.gz: 3a8fce56fd182a7cbdf64c88e9895166eb86fbad211d8616d1a034b4b2b81bbafe835cb228553d60773508866948331d42bdc48005304ae05e8697df51c1ac41
7
+ data.tar.gz: 3ded34fe6807cb4e1d88e295e8551ff664ece1c3736ecdee8c0d299b38d101f074d381ab54becf86ef1f22dc2278a86c2c7a0c292144c0eadade74602c7747db
data/.gitignore CHANGED
@@ -9,3 +9,6 @@
9
9
 
10
10
  # rspec failure tracking
11
11
  .rspec_status
12
+ *.gem
13
+ /pkg/
14
+ test_console_commands.md
data/CHANGELOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  ## [Unreleased]
2
2
 
3
- ## [0.1.0] - 2025-07-22
3
+ ## [0.1.1] - 2025-07-23
4
4
 
5
- - Initial release
5
+ ### Added
6
+ - CLI executable (`pw_tail`) for real-time event monitoring
7
+ - Enhanced cost calculation with accurate token counting and model-specific pricing
8
+ - Comprehensive test suite with 106 passing specs
9
+ - Support for major AI models with current pricing (GPT-4o, Claude-3, etc.)
10
+ - Rake task for retrying failed uploads (`rake pw:retry_failed`)
11
+
12
+ ### Features
13
+ - **Cost Calculation**: Accurate token counting with tiktoken integration and fallback
14
+ - **CLI Monitoring**: Real-time event streaming with JSON and human-readable formats
15
+ - **Reliability**: Disk-retry mechanism for failed uploads
16
+ - **Extensibility**: Adapter system for custom SDK integration
17
+
18
+ ### Supported Models
19
+ - OpenAI: gpt-4o, gpt-4o-mini, gpt-4-turbo, gpt-3.5-turbo
20
+ - Anthropic: claude-3-opus, claude-3-sonnet, claude-3-haiku
21
+ - Default fallback pricing for unknown models
22
+
23
+ ## [0.1.0] - 2025-07-23
24
+
25
+ ### Added
26
+ - Automatic SDK instrumentation for OpenAI, Anthropic, and Langchain
27
+ - YAML-based policy configuration with cost limits and regex patterns
28
+ - Policy alert system with non-blocking warnings and blocking rejections
29
+ - Automatic alert recording in events with structured alert data
30
+ - Asynchronous event uploads with gzip compression and disk-retry fallback
31
+
32
+ ### Features
33
+ - **Policy Enforcement**: Cost limits, regex pattern matching, alert generation
34
+ - **Event Capture**: Complete AI interaction data with metadata and alerts
35
+ - **Reliability**: Disk-retry mechanism for failed uploads
36
+ - **Extensibility**: Adapter system for custom SDK integration
data/Gemfile CHANGED
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- source "https://rubygems.org"
3
+ source 'https://rubygems.org'
4
4
 
5
5
  # Specify your gem's dependencies in prompt_warden.gemspec
6
6
  gemspec
7
7
 
8
- gem "irb"
9
- gem "rake", "~> 13.0"
8
+ gem 'irb'
9
+ gem 'rake', '~> 13.0'
10
10
 
11
- gem "rspec", "~> 3.0"
11
+ gem 'rspec', '~> 3.0'
data/Gemfile.lock CHANGED
@@ -1,9 +1,11 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- prompt_warden (0.1.0)
4
+ prompt_warden (0.1.1)
5
5
  activesupport (>= 6.1)
6
6
  faraday (~> 2.9)
7
+ faraday-retry (~> 2.2)
8
+ yaml
7
9
 
8
10
  GEM
9
11
  remote: https://rubygems.org/
@@ -21,12 +23,17 @@ GEM
21
23
  securerandom (>= 0.3)
22
24
  tzinfo (~> 2.0, >= 2.0.5)
23
25
  uri (>= 0.13.1)
26
+ addressable (2.8.7)
27
+ public_suffix (>= 2.0.2, < 7.0)
24
28
  ast (2.4.3)
25
29
  base64 (0.3.0)
26
30
  benchmark (0.4.1)
27
31
  bigdecimal (3.2.2)
28
32
  concurrent-ruby (1.3.5)
29
33
  connection_pool (2.5.3)
34
+ crack (1.0.0)
35
+ bigdecimal
36
+ rexml
30
37
  date (3.4.1)
31
38
  diff-lcs (1.6.2)
32
39
  drb (2.2.3)
@@ -37,6 +44,9 @@ GEM
37
44
  logger
38
45
  faraday-net_http (3.4.1)
39
46
  net-http (>= 0.5.0)
47
+ faraday-retry (2.3.2)
48
+ faraday (~> 2.0)
49
+ hashdiff (1.2.0)
40
50
  i18n (1.14.7)
41
51
  concurrent-ruby (~> 1.0)
42
52
  io-console (0.8.1)
@@ -62,6 +72,7 @@ GEM
62
72
  psych (5.2.6)
63
73
  date
64
74
  stringio
75
+ public_suffix (6.0.2)
65
76
  racc (1.8.1)
66
77
  rainbow (3.1.1)
67
78
  rake (13.3.0)
@@ -71,6 +82,7 @@ GEM
71
82
  regexp_parser (2.10.0)
72
83
  reline (0.6.2)
73
84
  io-console (~> 0.5)
85
+ rexml (3.4.1)
74
86
  rspec (3.13.1)
75
87
  rspec-core (~> 3.13.0)
76
88
  rspec-expectations (~> 3.13.0)
@@ -114,12 +126,18 @@ GEM
114
126
  ruby-progressbar (1.13.0)
115
127
  securerandom (0.4.1)
116
128
  stringio (3.1.7)
129
+ timecop (0.9.10)
117
130
  tzinfo (2.0.6)
118
131
  concurrent-ruby (~> 1.0)
119
132
  unicode-display_width (3.1.4)
120
133
  unicode-emoji (~> 4.0, >= 4.0.4)
121
134
  unicode-emoji (4.0.4)
122
135
  uri (1.0.3)
136
+ webmock (3.25.1)
137
+ addressable (>= 2.8.0)
138
+ crack (>= 0.3.2)
139
+ hashdiff (>= 0.4.0, < 2.0.0)
140
+ yaml (0.2.1)
123
141
 
124
142
  PLATFORMS
125
143
  arm64-darwin-23
@@ -133,6 +151,8 @@ DEPENDENCIES
133
151
  rspec (~> 3.12, ~> 3.0)
134
152
  rubocop (~> 1.60)
135
153
  rubocop-rspec (~> 2.26)
154
+ timecop (~> 0.9)
155
+ webmock (~> 3.20)
136
156
 
137
157
  BUNDLED WITH
138
158
  2.7.0
data/README.md CHANGED
@@ -1,43 +1,241 @@
1
1
  # PromptWarden
2
2
 
3
- TODO: Delete this and the text below, and describe your gem
3
+ **Record, audit, and guard AI prompt usage** with automatic SDK instrumentation, policy enforcement, and real-time monitoring.
4
4
 
5
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/prompt_warden`. To experiment with that code, run `bin/console` for an interactive prompt.
5
+ ## Features
6
+
7
+ - **Automatic SDK Capture**: Zero-code integration with OpenAI, Anthropic, and Langchain
8
+ - **Policy Guardrails**: YAML-based rules for cost limits, regex patterns, and alerts
9
+ - **Enhanced Cost Calculation**: Accurate token counting and model-specific pricing
10
+ - **Real-time Monitoring**: CLI tool for live event streaming and filtering
11
+ - **Alert System**: Non-blocking warnings and blocking rejections based on patterns
12
+ - **Automatic Alert Recording**: Alerts included in events and uploaded to SaaS
13
+ - **Asynchronous Uploads**: Batched, gzipped events with disk-retry fallback
6
14
 
7
15
  ## Installation
8
16
 
9
- TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
17
+ ```bash
18
+ gem install prompt_warden
19
+ ```
20
+
21
+ Or add to your Gemfile:
10
22
 
11
- Install the gem and add to the application's Gemfile by executing:
23
+ ```ruby
24
+ gem 'prompt_warden'
25
+ ```
12
26
 
13
- ```bash
14
- bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
27
+ ## Quick Start
28
+
29
+ 1. **Configure** (in your app's initializer):
30
+
31
+ ```ruby
32
+ PromptWarden.configure do |config|
33
+ config.project_token = 'your-project-token'
34
+ config.api_url = 'https://your-saas.com/api/v1/ingest'
35
+ end
36
+ ```
37
+
38
+ 2. **Create Policy** (`config/promptwarden.yml`):
39
+
40
+ ```yaml
41
+ max_cost_usd: 0.50 # Block if projected call cost > $0.50
42
+ reject_if_regex:
43
+ - /password/i
44
+ - /(ssn|social\s*security)/i
45
+ warn_if_regex:
46
+ - /\bETA\b/i
15
47
  ```
16
48
 
17
- If bundler is not being used to manage dependencies, install the gem by executing:
49
+ 3. **Use AI SDKs** (automatically instrumented):
50
+
51
+ ```ruby
52
+ # OpenAI
53
+ client = OpenAI::Client.new
54
+ response = client.chat(parameters: {
55
+ model: "gpt-4o",
56
+ messages: [{ role: "user", content: "What is the ETA?" }]
57
+ })
58
+
59
+ # Anthropic
60
+ client = Anthropic::Client.new
61
+ response = client.messages(
62
+ model: "claude-3-opus-20240229",
63
+ max_tokens: 1000,
64
+ messages: [{ role: "user", content: "What is the ETA?" }]
65
+ )
66
+ ```
67
+
68
+ ## CLI Tool
69
+
70
+ Monitor events in real-time with the `pw_tail` command:
18
71
 
19
72
  ```bash
20
- gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
73
+ # Follow all events
74
+ ./bin/pw_tail
75
+
76
+ # Show only events with alerts
77
+ ./bin/pw_tail --alerts
78
+
79
+ # Filter by model
80
+ ./bin/pw_tail --model gpt-4o
81
+
82
+ # Show events above cost threshold
83
+ ./bin/pw_tail --cost 0.01
84
+
85
+ # Filter by status
86
+ ./bin/pw_tail --status failed
87
+
88
+ # Limit number of events
89
+ ./bin/pw_tail --limit 10
90
+
91
+ # Output in JSON format
92
+ ./bin/pw_tail --json
93
+
94
+ # Show recent events without following
95
+ ./bin/pw_tail --no-follow
21
96
  ```
22
97
 
23
- ## Usage
98
+ ### CLI Output Format
24
99
 
25
- TODO: Write usage instructions here
100
+ ```
101
+ 10:30:00 gpt-4o $0.005 ok [⚠️ /ETA/i] | What is the ETA for this project?
102
+ 10:31:15 claude-3 $0.75 ok [💰 >$0.5] | How much does this cost?
103
+ 10:32:30 gpt-4o $0.001 ok | Simple question without alerts
104
+ ```
26
105
 
27
- ## Development
106
+ ## Policy Features
28
107
 
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
108
+ ### Cost Limits
109
+ ```yaml
110
+ max_cost_usd: 0.50 # Block requests exceeding $0.50
111
+ ```
30
112
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
113
+ ### Regex Patterns
114
+ ```yaml
115
+ reject_if_regex: # Block requests matching patterns
116
+ - /password/i
117
+ - /(ssn|social\s*security)/i
32
118
 
33
- ## Contributing
119
+ warn_if_regex: # Log warnings for patterns
120
+ - /\bETA\b/i
121
+ - /urgent/i
122
+ ```
34
123
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/prompt_warden. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/prompt_warden/blob/main/CODE_OF_CONDUCT.md).
124
+ ### Programmatic Checks
125
+ ```ruby
126
+ # Check for alerts (non-blocking)
127
+ alerts = PromptWarden::Policy.instance.check_alerts(
128
+ prompt: "What is the ETA?",
129
+ cost_estimate: 0.005
130
+ )
131
+
132
+ # Check for blocks (raises PolicyError)
133
+ PromptWarden::Policy.instance.check!(
134
+ prompt: "What is the password?",
135
+ cost_estimate: 0.001
136
+ )
137
+ ```
36
138
 
37
- ## License
139
+ ## Enhanced Cost Calculation
38
140
 
39
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
141
+ PromptWarden provides accurate cost calculation with:
40
142
 
41
- ## Code of Conduct
143
+ - **Model-specific pricing** for OpenAI and Anthropic models
144
+ - **Token counting** with tiktoken integration for OpenAI models
145
+ - **Response token integration** for accurate post-request costs
146
+ - **Fallback estimation** for unknown models
147
+
148
+ ```ruby
149
+ # Calculate cost for a prompt
150
+ cost = PromptWarden.calculate_cost(
151
+ prompt: "Explain quantum computing",
152
+ model: "gpt-4o"
153
+ )
154
+
155
+ # Calculate cost with actual response tokens
156
+ actual_cost = PromptWarden.calculate_cost(
157
+ prompt: "Explain quantum computing",
158
+ model: "gpt-4o",
159
+ response_tokens: 150
160
+ )
161
+ ```
162
+
163
+ ### Supported Models
164
+
165
+ **OpenAI Models:**
166
+ - `gpt-4o` ($0.0025/1K input, $0.01/1K output)
167
+ - `gpt-4o-mini` ($0.00015/1K input, $0.0006/1K output)
168
+ - `gpt-4-turbo` ($0.01/1K input, $0.03/1K output)
169
+ - `gpt-3.5-turbo` ($0.0005/1K input, $0.0015/1K output)
170
+
171
+ **Anthropic Models:**
172
+ - `claude-3-opus-20240229` ($0.015/1K input, $0.075/1K output)
173
+ - `claude-3-sonnet-20240229` ($0.003/1K input, $0.015/1K output)
174
+ - `claude-3-haiku-20240307` ($0.00025/1K input, $0.00125/1K output)
175
+
176
+ ## Supported SDKs
177
+
178
+ - **OpenAI**: `openai` gem
179
+ - **Anthropic**: `anthropic` gem
180
+ - **Langchain**: `langchain` gem
181
+
182
+ ## Gem vs SaaS
183
+
184
+ **PromptWarden Gem** (this repository):
185
+ - Local policy enforcement
186
+ - Event capture and buffering
187
+ - Enhanced cost calculation
188
+ - Asynchronous uploads to SaaS
189
+ - CLI monitoring tool
190
+ - Disk-retry for failed uploads
191
+
192
+ **PromptWarden SaaS** (separate application):
193
+ - Data storage and retention
194
+ - Analytics dashboards
195
+ - Advanced alerting (Slack, email)
196
+ - User and project management
197
+ - Cost tracking and reporting
198
+
199
+ ## Event Structure
200
+
201
+ Events are automatically captured and include:
202
+
203
+ ```json
204
+ {
205
+ "id": "uuid",
206
+ "prompt": "What is the ETA?",
207
+ "response": "The ETA is 2 weeks",
208
+ "model": "gpt-4o",
209
+ "latency_ms": 1250,
210
+ "cost_usd": 0.005,
211
+ "status": "ok",
212
+ "timestamp": "2024-01-15T10:30:00Z",
213
+ "alerts": [
214
+ {
215
+ "type": "regex",
216
+ "rule": "/ETA/i",
217
+ "level": "warn"
218
+ }
219
+ ]
220
+ }
221
+ ```
222
+
223
+ ## Development
224
+
225
+ ```bash
226
+ # Install dependencies
227
+ bundle install
228
+
229
+ # Run tests
230
+ bundle exec rspec
231
+
232
+ # Run CLI tests
233
+ bundle exec rspec spec/cli_spec.rb
234
+
235
+ # Test cost calculation
236
+ ruby test_cost_calculation.rb
237
+ ```
238
+
239
+ ## License
42
240
 
43
- Everyone interacting in the PromptWarden project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/prompt_warden/blob/main/CODE_OF_CONDUCT.md).
241
+ MIT License - see LICENSE file for details.
data/Rakefile CHANGED
@@ -1,8 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "bundler/gem_tasks"
4
- require "rspec/core/rake_task"
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
5
 
6
6
  RSpec::Core::RakeTask.new(:spec)
7
7
 
8
8
  task default: :spec
9
+
10
+ desc 'Retry failed PromptWarden uploads from disk'
11
+ namespace :pw do
12
+ task :retry_failed do
13
+ require_relative './lib/prompt_warden'
14
+
15
+ # Configure with default values if not already configured
16
+ unless PromptWarden.configuration.project_token
17
+ PromptWarden.configure do |c|
18
+ c.project_token = ENV['PROMPT_WARDEN_TOKEN'] || 'default_token'
19
+ c.api_url = ENV['PROMPT_WARDEN_API'] || 'https://httpbin.org/post'
20
+ end
21
+ end
22
+
23
+ PromptWarden::Uploader.retry_failed!
24
+ end
25
+ end
data/bin/console CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require "bundler/setup"
5
- require "prompt_warden"
4
+ require 'bundler/setup'
5
+ require 'prompt_warden'
6
6
 
7
7
  # You can add fixtures and/or initialization code here to make experimenting
8
8
  # with your gem easier. You can also use a different console, if you like.
9
9
 
10
- require "irb"
10
+ require 'irb'
11
11
  IRB.start(__FILE__)
data/bin/pw_tail ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'prompt_warden'
5
+ require_relative '../lib/prompt_warden/cli'
6
+
7
+ # Run the CLI
8
+ PromptWarden::CLI::Tail.run
@@ -0,0 +1,22 @@
1
+ # PromptWarden Policy Configuration
2
+ # This file configures guard-rails for AI prompt usage
3
+
4
+ # Cost limits - block if projected call cost exceeds this amount
5
+ max_cost_usd: 0.50
6
+
7
+ # Reject patterns - block execution if prompt matches any of these regexes
8
+ reject_if_regex:
9
+ - /password/i
10
+ - /(ssn|social\s*security)/i
11
+ - /credit\s*card/i
12
+ - /api\s*key/i
13
+
14
+ # Alert patterns - log alerts if prompt matches any of these regexes
15
+ # (does not block execution, but creates alerts in events)
16
+ warn_if_regex:
17
+ - /\bETA\b/i
18
+ - /deadline/i
19
+ - /urgent/i
20
+ - /asap/i
21
+ - /confidential/i
22
+ - /internal\s*use\s*only/i
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PromptWarden
4
+ module Adapter
5
+ ENTRY = Struct.new(:gem_name, :const_path, :block, :loaded)
6
+ REGISTRY = []
7
+
8
+ # public API ----------------------------------------------------------
9
+ def self.map(gem_name, const_path, &block)
10
+ REGISTRY << ENTRY.new(gem_name, const_path, block, false)
11
+ try_load(gem_name) # run now if gem already active
12
+ end
13
+
14
+ def self.auto_load_all!
15
+ Gem.loaded_specs.keys.each { |name| try_load(name) }
16
+ PromptWarden.configuration.run_pending_adapters
17
+ end
18
+
19
+ # internal ------------------------------------------------------------
20
+ def self.try_load(gem_name)
21
+ entry = REGISTRY.find { |e| e.gem_name == gem_name }
22
+ return unless entry && !entry.loaded
23
+
24
+ return unless Gem.loaded_specs.key?(gem_name) || const_path_defined?(entry.const_path)
25
+
26
+ entry.block.call
27
+ entry.loaded = true
28
+ end
29
+
30
+ def self.const_path_defined?(path)
31
+ names = path.split('::')
32
+ mod = Object
33
+ names.each do |name|
34
+ return false unless mod.const_defined?(name, false)
35
+
36
+ mod = mod.const_get(name)
37
+ end
38
+ true
39
+ end
40
+ private_class_method :const_path_defined?
41
+ end
42
+ end
43
+
44
+ # built‑in mappings -------------------------------------------------------
45
+ PromptWarden::Adapter.map('openai', 'OpenAI::Client') do
46
+ require_relative 'instrumentation/openai'
47
+ rescue Exception
48
+ raise
49
+ end
50
+ PromptWarden::Adapter.map('anthropic', 'Anthropic::Client') do
51
+ require_relative 'instrumentation/anthropic'
52
+ rescue Exception
53
+ raise
54
+ end
55
+ PromptWarden::Adapter.map('langchain', 'Langchain::LLM::Base') do
56
+ require_relative 'instrumentation/langchain'
57
+ rescue Exception
58
+ raise
59
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'zlib'
5
+
6
+ module PromptWarden
7
+ class Buffer
8
+ def initialize(cfg = PromptWarden.configuration)
9
+ @cfg = cfg
10
+ @events = []
11
+ @bytes = 0
12
+ @mutex = Mutex.new
13
+ start_timer
14
+ end
15
+
16
+ # -- Public ----------------------------------------------------------
17
+
18
+ # Enqueue an Event (or Hash). Flushes automatically when batch_bytes hit.
19
+ def push(event)
20
+ json = JSON.generate(event.to_h)
21
+ should_flush = false
22
+
23
+ @mutex.synchronize do
24
+ @events << json
25
+ @bytes += json.bytesize
26
+ should_flush = @bytes >= @cfg.batch_bytes
27
+ end
28
+
29
+ flush! if should_flush
30
+ end
31
+
32
+ # Manual flush
33
+ def flush!
34
+ batch = nil
35
+
36
+ @mutex.synchronize do
37
+ return if @events.empty?
38
+
39
+ batch = @events.join("\n")
40
+ @events.clear
41
+ @bytes = 0
42
+ end
43
+
44
+ compressed = Zlib.gzip(batch)
45
+ Uploader.instance.enqueue(compressed)
46
+ end
47
+
48
+ private
49
+
50
+ # Flushes buffer every flush_interval seconds
51
+ def start_timer
52
+ Thread.new do
53
+ loop do
54
+ sleep @cfg.flush_interval
55
+ flush!
56
+ end
57
+ end.tap { |t| t.name = 'PromptWarden::BufferFlusher' }
58
+ end
59
+ end
60
+ end