supalog 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c56751e0f6bd6c2b7fbd83d1affe843efb301c079c0f34fa9266a136b7075cac
4
+ data.tar.gz: 38ab5f1ad84e30db98076365905cbc8f57d9d1a132458d4aad671a3fadfed2f1
5
+ SHA512:
6
+ metadata.gz: 1888b328208392bfe29c03105b8e1e84dc06828f047ff4fa2b388cb90c55fc0da145ef0f5d996412d87e5f570ed75d559e3e9f5c3f2fb7c48a15bd766544dd62
7
+ data.tar.gz: db3dc693415a8b549c06aa5f5aa41404c05ace86df14320472f439085b2f47fd6cbaa0cf3f5f19f9e7a4701ee0e5702aa880ce5ee5928f29a4bf00a2fec203d4
@@ -0,0 +1,8 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(bundle exec:*)",
5
+ "Bash(bundle install:*)"
6
+ ]
7
+ }
8
+ }
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2026-03-17
4
+
5
+ - Initial release
data/CLAUDE.md ADDED
@@ -0,0 +1,73 @@
1
+ # CLAUDE.md
2
+
3
+ ## Project Overview
4
+
5
+ Supalog is a Ruby gem that ships logs from Rails applications to the Supalog platform (www.supalog.dev). It provides a drop-in Rails logger that buffers log entries in memory and flushes them in batches to the Supalog ingest API via a background thread.
6
+
7
+ **Zero dependencies** — uses only `Net::HTTP` from Ruby stdlib.
8
+
9
+ ## Ingest API
10
+
11
+ The gem posts to the Supalog ingest API:
12
+
13
+ - **Endpoint:** `POST /api/logs`
14
+ - **Auth:** `X-Api-Key` header with the project's API key
15
+ - **Content-Type:** `application/json`
16
+ - **Payload:**
17
+ ```json
18
+ {
19
+ "logs": [
20
+ {
21
+ "level": "info",
22
+ "message": "Started GET / for 127.0.0.1",
23
+ "metadata": { "request_id": "abc123" },
24
+ "timestamp": "2026-03-17T14:30:00Z"
25
+ }
26
+ ]
27
+ }
28
+ ```
29
+ - **Response:** `201 Created` on success, `401 Unauthorized` on invalid API key
30
+
31
+ ## Architecture
32
+
33
+ - **`Supalog`** — top-level module. Holds configuration, manages the buffer and background flush thread via singleton methods.
34
+ - **`Supalog::Buffer`** — thread-safe in-memory array. Accepts log entries, returns and clears batch on flush.
35
+ - **`Supalog::LogSubscriber`** — wraps `Rails.logger#add` to intercept log calls, writes to the buffer, and passes through to the original logger.
36
+ - **`Supalog::Transport`** — delivers batches to the Supalog ingest API via `Net::HTTP`.
37
+ - **`Supalog::Railtie`** — auto-configures in Rails apps. Wires up the logger when `api_key` is present.
38
+
39
+ ## Expected Usage
40
+
41
+ ```ruby
42
+ # config/initializers/supalog.rb
43
+ Supalog.configure do |config|
44
+ config.api_key = ENV["SUPALOG_API_KEY"]
45
+ config.url = "https://www.supalog.dev" # optional, this is the default
46
+ config.flush_interval = 5 # seconds, optional, default 5
47
+ config.batch_size = 100 # optional, default 100
48
+ end
49
+ ```
50
+
51
+ Once configured, Rails logs automatically flow to Supalog. No other code changes needed.
52
+
53
+ ## Design Constraints
54
+
55
+ - Zero external dependencies — only `net/http`, `json`, `uri` from stdlib
56
+ - Thread-safe buffer (Mutex)
57
+ - Background thread flushes on interval OR when batch_size is reached
58
+ - Graceful shutdown — flush remaining buffer on `at_exit`
59
+ - Silent failures — never raise exceptions that crash the host app. Log errors to STDERR.
60
+ - Works with Ruby 3.0+
61
+
62
+ ## Common Commands
63
+
64
+ - `bundle exec rspec` — run tests
65
+ - `bundle exec rake build` — build the gem
66
+ - `bundle exec rake install` — install locally
67
+ - `bundle exec rake release` — release to RubyGems
68
+
69
+ ## Code Style
70
+
71
+ - Use double quotes (`"`)
72
+ - No external dependencies
73
+ - Keep it minimal — this gem should be small and focused
@@ -0,0 +1,10 @@
1
+ # Code of Conduct
2
+
3
+ "supalog" follows [The Ruby Community Conduct Guideline](https://www.ruby-lang.org/en/conduct) in all "collaborative space", which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.):
4
+
5
+ * Participants will be tolerant of opposing views.
6
+ * Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
7
+ * When interpreting the words and actions of others, participants should always assume good intentions.
8
+ * Behaviour which can be reasonably considered harassment will not be tolerated.
9
+
10
+ If you have any concerns about behaviour within this project, please contact us at ["chris@typefast.co"](mailto:"chris@typefast.co").
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Chris Jeon
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # Supalog
2
+
3
+ TODO: Delete this and the text below, and describe your gem
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/supalog`. To experiment with that code, run `bin/console` for an interactive prompt.
6
+
7
+ ## Installation
8
+
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.
10
+
11
+ Install the gem and add to the application's Gemfile by executing:
12
+
13
+ ```bash
14
+ bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
15
+ ```
16
+
17
+ If bundler is not being used to manage dependencies, install the gem by executing:
18
+
19
+ ```bash
20
+ gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
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.
30
+
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).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/supalog. 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]/supalog/blob/master/CODE_OF_CONDUCT.md).
36
+
37
+ ## License
38
+
39
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
40
+
41
+ ## Code of Conduct
42
+
43
+ Everyone interacting in the Supalog project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/supalog/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Supalog
4
+ class Buffer
5
+ def initialize(batch_size:, flush_interval:, &on_flush)
6
+ @batch_size = batch_size
7
+ @flush_interval = flush_interval
8
+ @on_flush = on_flush
9
+ @mutex = Mutex.new
10
+ @entries = []
11
+ @stopped = false
12
+ start_flush_thread
13
+ end
14
+
15
+ def push(entry)
16
+ should_flush = false
17
+
18
+ @mutex.synchronize do
19
+ @entries << entry
20
+ should_flush = @entries.size >= @batch_size
21
+ end
22
+
23
+ flush! if should_flush
24
+ end
25
+
26
+ def flush!
27
+ batch = nil
28
+
29
+ @mutex.synchronize do
30
+ return if @entries.empty?
31
+
32
+ batch = @entries.dup
33
+ @entries.clear
34
+ end
35
+
36
+ @on_flush.call(batch) if batch
37
+ rescue => e
38
+ $stderr.puts "[Supalog] Flush error: #{e.message}"
39
+ end
40
+
41
+ def stop
42
+ @stopped = true
43
+ @flush_thread&.join(5)
44
+ @flush_thread = nil
45
+ end
46
+
47
+ def size
48
+ @mutex.synchronize { @entries.size }
49
+ end
50
+
51
+ private
52
+
53
+ def start_flush_thread
54
+ @flush_thread = Thread.new do
55
+ loop do
56
+ sleep @flush_interval
57
+ break if @stopped
58
+
59
+ flush!
60
+ end
61
+ end
62
+ @flush_thread.abort_on_exception = false
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Supalog
4
+ class Configuration
5
+ attr_accessor :api_key, :url, :batch_size, :flush_interval
6
+
7
+ def initialize
8
+ @api_key = nil
9
+ @url = "https://www.supalog.dev"
10
+ @batch_size = 100
11
+ @flush_interval = 5
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Supalog
4
+ class LogSubscriber
5
+ SEVERITY_MAP = {
6
+ 0 => "debug",
7
+ 1 => "info",
8
+ 2 => "warn",
9
+ 3 => "error",
10
+ 4 => "fatal",
11
+ 5 => "unknown"
12
+ }.freeze
13
+
14
+ def self.attach_logger!(logger)
15
+ return unless logger
16
+
17
+ original_add = logger.method(:add)
18
+ severity_map = SEVERITY_MAP
19
+
20
+ logger.define_singleton_method(:add) do |severity, message = nil, progname = nil, &block|
21
+ msg = message || (block ? block.call : progname)
22
+
23
+ if msg
24
+ Supalog.push({
25
+ "level" => severity_map[severity] || "unknown",
26
+ "message" => msg.to_s,
27
+ "metadata" => {},
28
+ "timestamp" => Time.now.utc.iso8601(3)
29
+ })
30
+ end
31
+
32
+ original_add.call(severity, message, progname, &block)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "log_subscriber"
4
+
5
+ module Supalog
6
+ class Railtie < Rails::Railtie
7
+ initializer "supalog.configure_logging" do
8
+ if Supalog.configuration.api_key
9
+ Supalog.start!
10
+ Supalog::LogSubscriber.attach_logger!(Rails.logger)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "uri"
5
+ require "json"
6
+
7
+ module Supalog
8
+ class Transport
9
+ CONNECT_TIMEOUT = 5
10
+ READ_TIMEOUT = 10
11
+
12
+ def self.deliver(batch, configuration)
13
+ uri = URI.join(configuration.url, "/api/logs")
14
+
15
+ http = Net::HTTP.new(uri.host, uri.port)
16
+ http.use_ssl = uri.scheme == "https"
17
+ http.open_timeout = CONNECT_TIMEOUT
18
+ http.read_timeout = READ_TIMEOUT
19
+
20
+ request = Net::HTTP::Post.new(uri.path)
21
+ request["Content-Type"] = "application/json"
22
+ request["X-Api-Key"] = configuration.api_key
23
+ request.body = JSON.generate({ "logs" => batch })
24
+
25
+ response = http.request(request)
26
+
27
+ unless response.is_a?(Net::HTTPSuccess)
28
+ $stderr.puts "[Supalog] Ingest API responded with #{response.code}: #{response.body}"
29
+ end
30
+ rescue => e
31
+ $stderr.puts "[Supalog] Transport error: #{e.message}"
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Supalog
4
+ VERSION = "0.1.0"
5
+ end
data/lib/supalog.rb ADDED
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "supalog/version"
4
+ require_relative "supalog/configuration"
5
+ require_relative "supalog/buffer"
6
+ require_relative "supalog/transport"
7
+
8
+ module Supalog
9
+ class Error < StandardError; end
10
+
11
+ class << self
12
+ def configuration
13
+ @configuration ||= Configuration.new
14
+ end
15
+
16
+ def configure
17
+ yield(configuration)
18
+ start!
19
+ end
20
+
21
+ def start!
22
+ return if @started
23
+
24
+ @buffer = Buffer.new(
25
+ batch_size: configuration.batch_size,
26
+ flush_interval: configuration.flush_interval
27
+ ) { |batch| Transport.deliver(batch, configuration) }
28
+
29
+ at_exit { shutdown }
30
+ @started = true
31
+ end
32
+
33
+ def buffer
34
+ @buffer
35
+ end
36
+
37
+ def push(entry)
38
+ @buffer&.push(entry)
39
+ end
40
+
41
+ def shutdown
42
+ @buffer&.flush!
43
+ @buffer&.stop
44
+ @started = false
45
+ end
46
+
47
+ def reset!
48
+ shutdown
49
+ @configuration = Configuration.new
50
+ @buffer = nil
51
+ @started = false
52
+ end
53
+ end
54
+ end
55
+
56
+ require_relative "supalog/railtie" if defined?(Rails::Railtie)
data/sig/supalog.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Supalog
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: supalog
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Chris Jeon
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: A drop-in Rails logger that buffers log entries and flushes them in batches
13
+ to the Supalog ingest API via a background thread.
14
+ email:
15
+ - chris@typefast.co
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".claude/settings.local.json"
21
+ - CHANGELOG.md
22
+ - CLAUDE.md
23
+ - CODE_OF_CONDUCT.md
24
+ - LICENSE.txt
25
+ - README.md
26
+ - Rakefile
27
+ - lib/supalog.rb
28
+ - lib/supalog/buffer.rb
29
+ - lib/supalog/configuration.rb
30
+ - lib/supalog/log_subscriber.rb
31
+ - lib/supalog/railtie.rb
32
+ - lib/supalog/transport.rb
33
+ - lib/supalog/version.rb
34
+ - sig/supalog.rbs
35
+ homepage: https://supalog.dev
36
+ licenses:
37
+ - MIT
38
+ metadata:
39
+ homepage_uri: https://supalog.dev
40
+ source_code_uri: https://github.com/chrisjeon/supalog-rb
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: 3.0.0
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ requirements: []
55
+ rubygems_version: 4.0.3
56
+ specification_version: 4
57
+ summary: Ship Rails logs to the Supalog platform
58
+ test_files: []