solid_log-core 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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +176 -0
  4. data/Rakefile +11 -0
  5. data/db/log_migrate/20251222000001_create_solid_log_raw.rb +15 -0
  6. data/db/log_migrate/20251222000002_create_solid_log_entries.rb +29 -0
  7. data/db/log_migrate/20251222000004_create_solid_log_fields.rb +17 -0
  8. data/db/log_migrate/20251222000005_create_solid_log_tokens.rb +13 -0
  9. data/db/log_migrate/20251222000006_create_solid_log_facet_cache.rb +13 -0
  10. data/db/log_migrate/20251222000007_create_solid_log_fts_triggers.rb +41 -0
  11. data/db/log_structure_mysql.sql +96 -0
  12. data/db/log_structure_postgresql.sql +118 -0
  13. data/db/log_structure_sqlite.sql +123 -0
  14. data/lib/generators/solid_log/install/install_generator.rb +134 -0
  15. data/lib/generators/solid_log/install/templates/solid_log.rb.tt +133 -0
  16. data/lib/solid_log/adapters/adapter_factory.rb +34 -0
  17. data/lib/solid_log/adapters/base_adapter.rb +88 -0
  18. data/lib/solid_log/adapters/mysql_adapter.rb +163 -0
  19. data/lib/solid_log/adapters/postgresql_adapter.rb +141 -0
  20. data/lib/solid_log/adapters/sqlite_adapter.rb +149 -0
  21. data/lib/solid_log/core/client/buffer.rb +112 -0
  22. data/lib/solid_log/core/client/configuration.rb +31 -0
  23. data/lib/solid_log/core/client/http.rb +89 -0
  24. data/lib/solid_log/core/client/lograge_formatter.rb +99 -0
  25. data/lib/solid_log/core/client/retry_handler.rb +48 -0
  26. data/lib/solid_log/core/client.rb +138 -0
  27. data/lib/solid_log/core/configuration.rb +60 -0
  28. data/lib/solid_log/core/services/correlation_service.rb +74 -0
  29. data/lib/solid_log/core/services/field_analyzer.rb +108 -0
  30. data/lib/solid_log/core/services/health_service.rb +151 -0
  31. data/lib/solid_log/core/services/retention_service.rb +72 -0
  32. data/lib/solid_log/core/services/search_service.rb +269 -0
  33. data/lib/solid_log/core/version.rb +5 -0
  34. data/lib/solid_log/core.rb +106 -0
  35. data/lib/solid_log/direct_logger.rb +197 -0
  36. data/lib/solid_log/models/entry.rb +185 -0
  37. data/lib/solid_log/models/facet_cache.rb +58 -0
  38. data/lib/solid_log/models/field.rb +100 -0
  39. data/lib/solid_log/models/raw_entry.rb +33 -0
  40. data/lib/solid_log/models/record.rb +5 -0
  41. data/lib/solid_log/models/token.rb +61 -0
  42. data/lib/solid_log/parser.rb +179 -0
  43. data/lib/solid_log/silence_middleware.rb +34 -0
  44. data/lib/solid_log-core.rb +2 -0
  45. metadata +244 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: '09b479494d3ac70c18b6d0611a5ee0879ff01b90c17108dcffe1eaa951a47f00'
4
+ data.tar.gz: ca5f502ca1f8c7b722f1c26b31c898bf3c0e04b512fbde82d0b1e1e888006e32
5
+ SHA512:
6
+ metadata.gz: 2da7bc4a35999dc410ef3951325e1c61c7e67ef4a7f5028d386bb397e676edc50528e2b46209a4999dc94f95cf6ff6b7025db7d7010683e2dd73b207dcac9333
7
+ data.tar.gz: dff9a33b078a75d0019bfba2d9371b9d4804ce6bcfb52ed6be75bc2ce9a3892ba57d679885fd64ac56c4904945d101078eedfbbb709114d662b0978b57218431
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright Dan Loman
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,176 @@
1
+ # SolidLog::Core
2
+
3
+ Core models, database adapters, parser, DirectLogger, and HTTP client for the SolidLog logging system.
4
+
5
+ ## Overview
6
+
7
+ `solid_log-core` provides the shared foundation for `solid_log-service` and `solid_log-ui` gems:
8
+
9
+ - **Models**: `RawEntry`, `Entry`, `Token`, `Field`, `FacetCache`
10
+ - **Database Adapters**: SQLite, PostgreSQL, MySQL adapters with database-specific optimizations
11
+ - **DirectLogger**: High-performance batched logging for parent app (50,000+ logs/sec)
12
+ - **Parser**: Structured log parsing with field extraction
13
+ - **Service Objects**: `RetentionService`, `FieldAnalyzer`, `SearchService`, `CorrelationService`
14
+ - **HTTP Client**: Buffered log sender with retry logic for remote ingestion
15
+ - **Migrations**: Database schema in `db/log_migrate/`
16
+
17
+ ## Installation
18
+
19
+ Add this line to your application's Gemfile:
20
+
21
+ ```ruby
22
+ gem 'solid_log-core'
23
+
24
+ # Also ensure you have the database adapter gem for your database:
25
+ gem 'sqlite3', '>= 2.1' # For SQLite
26
+ # OR
27
+ gem 'pg', '>= 1.1' # For PostgreSQL
28
+ # OR
29
+ gem 'mysql2', '>= 0.5' # For MySQL
30
+ ```
31
+
32
+ **Dependencies:**
33
+ - `solid_log-core` requires only specific Rails components: `activerecord`, `activesupport`, and `activejob`
34
+ - It does **NOT** require the full `rails` gem
35
+ - Database adapter gems are optional - install only what you need
36
+
37
+ ## Usage
38
+
39
+ This gem is typically used as a dependency for:
40
+ - **solid_log-service**: Standalone log ingestion service
41
+ - **solid_log-ui**: Web interface for viewing logs
42
+
43
+ ### DirectLogger (Recommended for Parent App)
44
+
45
+ **DirectLogger** writes logs directly to the database for maximum performance. Use this when your Rails app has direct database access.
46
+
47
+ ```ruby
48
+ # config/initializers/lograge.rb
49
+ Rails.application.configure do
50
+ config.lograge.enabled = true
51
+ config.lograge.formatter = Lograge::Formatters::Json.new
52
+
53
+ # Use DirectLogger for fast, batched database writes
54
+ config.lograge.logger = ActiveSupport::Logger.new(
55
+ SolidLog::DirectLogger.new(
56
+ batch_size: 100, # Batch size (default: 100)
57
+ flush_interval: 5, # Flush every 5 seconds
58
+ eager_flush_levels: [:error, :fatal] # Flush immediately on errors (crash safety)
59
+ )
60
+ )
61
+ end
62
+ ```
63
+
64
+ **Performance:**
65
+ - **16,882 logs/sec** with crash safety (SQLite + WAL mode)
66
+ - **56,660 logs/sec** without eager flush (risky - may lose crash logs)
67
+ - **9x faster** than individual database inserts
68
+ - **67x faster** than HTTP logging
69
+
70
+ **Configuration Options:**
71
+
72
+ ```ruby
73
+ SolidLog::DirectLogger.new(
74
+ batch_size: 100, # Logs per batch (default: 100)
75
+ flush_interval: 5, # Flush interval in seconds (default: 5)
76
+ eager_flush_levels: [:error, :fatal], # Flush these levels immediately (default: [:error, :fatal])
77
+ token_id: nil # Optional token for audit trail (default: nil or ENV['SOLIDLOG_TOKEN_ID'])
78
+ )
79
+ ```
80
+
81
+ **Crash Safety:**
82
+
83
+ By default, DirectLogger flushes error and fatal logs immediately to prevent losing the logs that explain WHY your app crashed:
84
+
85
+ ```ruby
86
+ # Safe (recommended for production)
87
+ logger = SolidLog::DirectLogger.new(
88
+ eager_flush_levels: [:error, :fatal]
89
+ )
90
+
91
+ # Maximum performance (risky - may lose crash context)
92
+ logger = SolidLog::DirectLogger.new(
93
+ eager_flush_levels: []
94
+ )
95
+
96
+ # Maximum safety (slower)
97
+ logger = SolidLog::DirectLogger.new(
98
+ batch_size: 10,
99
+ flush_interval: 1,
100
+ eager_flush_levels: [:debug, :info, :warn, :error, :fatal]
101
+ )
102
+ ```
103
+
104
+ **Environment Variables:**
105
+
106
+ DirectLogger supports optional token configuration via environment:
107
+
108
+ ```bash
109
+ export SOLIDLOG_TOKEN_ID=123 # Optional: for audit trail tracking
110
+ ```
111
+
112
+ See [BENCHMARK_RESULTS.md](BENCHMARK_RESULTS.md) for detailed performance analysis.
113
+
114
+ ### HTTP Client (For External Services)
115
+
116
+ For sending logs to a remote SolidLog service:
117
+
118
+ ```ruby
119
+ SolidLog::Core.configure_client do |config|
120
+ config.service_url = ENV['SOLIDLOG_SERVICE_URL']
121
+ config.token = ENV['SOLIDLOG_TOKEN']
122
+ config.app_name = 'web'
123
+ config.environment = Rails.env
124
+ end
125
+ ```
126
+
127
+ ## Database Setup
128
+
129
+ **Option 1: Run migrations**
130
+
131
+ ```bash
132
+ rails db:migrate
133
+ ```
134
+
135
+ **Option 2: Use provided structure file**
136
+
137
+ Choose the structure file for your database type:
138
+
139
+ ```bash
140
+ # For SQLite
141
+ rails db:schema:load SCHEMA=db/log_structure_sqlite.sql
142
+
143
+ # For PostgreSQL
144
+ rails db:schema:load SCHEMA=db/log_structure_postgresql.sql
145
+
146
+ # For MySQL
147
+ rails db:schema:load SCHEMA=db/log_structure_mysql.sql
148
+ ```
149
+
150
+ The structure files are optimized for each database with appropriate FTS implementations:
151
+ - **SQLite**: FTS5 virtual table
152
+ - **PostgreSQL**: tsvector with GIN index
153
+ - **MySQL**: FULLTEXT index
154
+
155
+ ### Enable WAL Mode for SQLite (Highly Recommended)
156
+
157
+ WAL (Write-Ahead Logging) mode provides **243% better performance** for crash-safe logging:
158
+
159
+ ```ruby
160
+ # config/initializers/solid_log.rb
161
+ ActiveRecord::Base.connected_to(role: :log) do
162
+ ActiveRecord::Base.connection.execute("PRAGMA journal_mode=WAL")
163
+ ActiveRecord::Base.connection.execute("PRAGMA synchronous=NORMAL")
164
+ end
165
+ ```
166
+
167
+ **Benefits:**
168
+ - **3.4x faster** eager flush (16,882 vs 4,923 logs/sec)
169
+ - Better concurrency (readers don't block writers)
170
+ - Recommended for all production deployments
171
+
172
+ See [BENCHMARK_RESULTS.md](BENCHMARK_RESULTS.md) for performance comparison.
173
+
174
+ ## License
175
+
176
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "lib"
6
+ t.libs << "test"
7
+ t.pattern = "test/**/*_test.rb"
8
+ t.verbose = false
9
+ end
10
+
11
+ task default: :test
@@ -0,0 +1,15 @@
1
+ class CreateSolidLogRaw < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :solid_log_raw do |t|
4
+ t.datetime :received_at, null: false, default: -> { "CURRENT_TIMESTAMP" }
5
+ t.integer :token_id # Nullable - only required for HTTP API auth, not DirectLogger
6
+ t.text :payload, null: false
7
+ t.boolean :parsed, default: false, null: false
8
+ t.datetime :parsed_at
9
+ end
10
+
11
+ add_index :solid_log_raw, [:parsed, :received_at], name: "idx_raw_unparsed"
12
+ add_index :solid_log_raw, :token_id, name: "idx_raw_token"
13
+ add_index :solid_log_raw, :received_at, name: "idx_raw_received"
14
+ end
15
+ end
@@ -0,0 +1,29 @@
1
+ class CreateSolidLogEntries < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :solid_log_entries do |t|
4
+ t.integer :raw_id, null: false
5
+ t.datetime :timestamp, null: false # When the log event occurred
6
+ t.datetime :created_at, null: false # When the entry was parsed/created
7
+ t.string :level, null: false
8
+ t.string :app
9
+ t.string :env
10
+ t.text :message
11
+ t.string :request_id
12
+ t.string :job_id
13
+ t.float :duration
14
+ t.integer :status_code
15
+ t.string :controller
16
+ t.string :action
17
+ t.string :path
18
+ t.string :method
19
+ t.text :extra_fields
20
+ end
21
+
22
+ add_index :solid_log_entries, :timestamp, order: { timestamp: :desc }, name: "idx_entries_timestamp"
23
+ add_index :solid_log_entries, :level, name: "idx_entries_level"
24
+ add_index :solid_log_entries, [:app, :env, :timestamp], order: { timestamp: :desc }, name: "idx_entries_app_env_time"
25
+ add_index :solid_log_entries, :request_id, name: "idx_entries_request"
26
+ add_index :solid_log_entries, :job_id, name: "idx_entries_job"
27
+ add_index :solid_log_entries, :raw_id, name: "idx_entries_raw"
28
+ end
29
+ end
@@ -0,0 +1,17 @@
1
+ class CreateSolidLogFields < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :solid_log_fields do |t|
4
+ t.string :name, null: false
5
+ t.string :field_type, null: false
6
+ t.string :filter_type, default: "multiselect", null: false
7
+ t.integer :usage_count, default: 0, null: false
8
+ t.datetime :last_seen_at
9
+ t.boolean :promoted, default: false, null: false
10
+ t.timestamps
11
+ end
12
+
13
+ add_index :solid_log_fields, :name, unique: true, name: "idx_fields_name"
14
+ add_index :solid_log_fields, :promoted, name: "idx_fields_promoted"
15
+ add_index :solid_log_fields, :usage_count, name: "idx_fields_usage"
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ class CreateSolidLogTokens < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :solid_log_tokens do |t|
4
+ t.string :name, null: false
5
+ t.string :token_hash, null: false
6
+ t.datetime :last_used_at
7
+ t.timestamps
8
+ end
9
+
10
+ add_index :solid_log_tokens, :token_hash, unique: true, name: "idx_tokens_hash"
11
+ add_index :solid_log_tokens, :name, name: "idx_tokens_name"
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ class CreateSolidLogFacetCache < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :solid_log_facet_cache do |t|
4
+ t.string :key_name, null: false
5
+ t.text :cache_value, null: false
6
+ t.datetime :expires_at
7
+ t.timestamps
8
+ end
9
+
10
+ add_index :solid_log_facet_cache, :key_name, unique: true, name: "idx_facet_key_name"
11
+ add_index :solid_log_facet_cache, :expires_at, name: "idx_facet_expires"
12
+ end
13
+ end
@@ -0,0 +1,41 @@
1
+ class CreateSolidLogFtsTriggers < ActiveRecord::Migration[8.0]
2
+ def up
3
+ # SQLite FTS5 implementation
4
+ execute <<~SQL
5
+ CREATE VIRTUAL TABLE solid_log_entries_fts USING fts5(
6
+ message,
7
+ extra_fields,
8
+ content='solid_log_entries',
9
+ content_rowid='id'
10
+ );
11
+ SQL
12
+
13
+ execute <<~SQL
14
+ CREATE TRIGGER solid_log_entries_fts_insert AFTER INSERT ON solid_log_entries BEGIN
15
+ INSERT INTO solid_log_entries_fts(rowid, message, extra_fields)
16
+ VALUES (new.id, new.message, new.extra_fields);
17
+ END;
18
+ SQL
19
+
20
+ execute <<~SQL
21
+ CREATE TRIGGER solid_log_entries_fts_update AFTER UPDATE ON solid_log_entries BEGIN
22
+ UPDATE solid_log_entries_fts
23
+ SET message = new.message, extra_fields = new.extra_fields
24
+ WHERE rowid = new.id;
25
+ END;
26
+ SQL
27
+
28
+ execute <<~SQL
29
+ CREATE TRIGGER solid_log_entries_fts_delete AFTER DELETE ON solid_log_entries BEGIN
30
+ DELETE FROM solid_log_entries_fts WHERE rowid = old.id;
31
+ END;
32
+ SQL
33
+ end
34
+
35
+ def down
36
+ execute "DROP TRIGGER IF EXISTS solid_log_entries_fts_delete"
37
+ execute "DROP TRIGGER IF EXISTS solid_log_entries_fts_update"
38
+ execute "DROP TRIGGER IF EXISTS solid_log_entries_fts_insert"
39
+ execute "DROP TABLE IF EXISTS solid_log_entries_fts"
40
+ end
41
+ end
@@ -0,0 +1,96 @@
1
+ -- SolidLog Database Structure for MySQL
2
+ -- Generated from migrations in db/log_migrate/
3
+
4
+ -- solid_log_raw: Fast ingestion table for raw log payloads
5
+ CREATE TABLE solid_log_raw (
6
+ id BIGINT AUTO_INCREMENT PRIMARY KEY,
7
+ received_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
8
+ token_id BIGINT NOT NULL,
9
+ payload TEXT NOT NULL,
10
+ parsed TINYINT(1) NOT NULL DEFAULT 0,
11
+ parsed_at DATETIME,
12
+ INDEX idx_raw_unparsed (parsed, received_at),
13
+ INDEX idx_raw_token (token_id),
14
+ INDEX idx_raw_received (received_at)
15
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
16
+
17
+ -- solid_log_entries: Parsed and indexed log entries for querying
18
+ CREATE TABLE solid_log_entries (
19
+ id BIGINT AUTO_INCREMENT PRIMARY KEY,
20
+ raw_id BIGINT NOT NULL,
21
+ timestamp DATETIME NOT NULL,
22
+ created_at DATETIME NOT NULL,
23
+ level VARCHAR(255) NOT NULL,
24
+ app VARCHAR(255),
25
+ env VARCHAR(255),
26
+ message TEXT,
27
+ request_id VARCHAR(255),
28
+ job_id VARCHAR(255),
29
+ duration DOUBLE,
30
+ status_code INT,
31
+ controller VARCHAR(255),
32
+ action VARCHAR(255),
33
+ path VARCHAR(255),
34
+ method VARCHAR(255),
35
+ extra_fields TEXT,
36
+ INDEX idx_entries_timestamp (timestamp DESC),
37
+ INDEX idx_entries_level (level),
38
+ INDEX idx_entries_app_env_time (app, env, timestamp DESC),
39
+ INDEX idx_entries_request (request_id),
40
+ INDEX idx_entries_job (job_id),
41
+ INDEX idx_entries_raw (raw_id),
42
+ FULLTEXT INDEX idx_entries_fts (message, extra_fields)
43
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
44
+
45
+ -- solid_log_fields: Field registry for tracking dynamic JSON fields
46
+ CREATE TABLE solid_log_fields (
47
+ id BIGINT AUTO_INCREMENT PRIMARY KEY,
48
+ name VARCHAR(255) NOT NULL,
49
+ field_type VARCHAR(255) NOT NULL,
50
+ filter_type VARCHAR(255) NOT NULL DEFAULT 'multiselect',
51
+ usage_count INT NOT NULL DEFAULT 0,
52
+ last_seen_at DATETIME,
53
+ promoted TINYINT(1) NOT NULL DEFAULT 0,
54
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
55
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
56
+ UNIQUE INDEX idx_fields_name (name),
57
+ INDEX idx_fields_promoted (promoted),
58
+ INDEX idx_fields_usage (usage_count)
59
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
60
+
61
+ -- solid_log_tokens: API tokens for log ingestion authentication
62
+ CREATE TABLE solid_log_tokens (
63
+ id BIGINT AUTO_INCREMENT PRIMARY KEY,
64
+ name VARCHAR(255) NOT NULL,
65
+ token_hash VARCHAR(255) NOT NULL,
66
+ last_used_at DATETIME,
67
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
68
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
69
+ UNIQUE INDEX idx_tokens_hash (token_hash),
70
+ INDEX idx_tokens_name (name)
71
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
72
+
73
+ -- solid_log_facet_cache: Cache for filter options to reduce DB load
74
+ CREATE TABLE solid_log_facet_cache (
75
+ id BIGINT AUTO_INCREMENT PRIMARY KEY,
76
+ key_name VARCHAR(255) NOT NULL,
77
+ cache_value TEXT NOT NULL,
78
+ expires_at DATETIME,
79
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
80
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
81
+ UNIQUE INDEX idx_facet_key_name (key_name),
82
+ INDEX idx_facet_expires (expires_at)
83
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
84
+
85
+ -- Schema migrations
86
+ CREATE TABLE IF NOT EXISTS schema_migrations (
87
+ version VARCHAR(255) PRIMARY KEY
88
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
89
+
90
+ INSERT INTO schema_migrations (version) VALUES
91
+ ('20251222000001'),
92
+ ('20251222000002'),
93
+ ('20251222000004'),
94
+ ('20251222000005'),
95
+ ('20251222000006'),
96
+ ('20251222000007');
@@ -0,0 +1,118 @@
1
+ -- SolidLog Database Structure for PostgreSQL
2
+ -- Generated from migrations in db/log_migrate/
3
+
4
+ -- solid_log_raw: Fast ingestion table for raw log payloads
5
+ CREATE TABLE solid_log_raw (
6
+ id BIGSERIAL PRIMARY KEY,
7
+ received_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
8
+ token_id BIGINT NOT NULL,
9
+ payload TEXT NOT NULL,
10
+ parsed BOOLEAN NOT NULL DEFAULT FALSE,
11
+ parsed_at TIMESTAMP
12
+ );
13
+
14
+ CREATE INDEX idx_raw_unparsed ON solid_log_raw(parsed, received_at);
15
+ CREATE INDEX idx_raw_token ON solid_log_raw(token_id);
16
+ CREATE INDEX idx_raw_received ON solid_log_raw(received_at);
17
+
18
+ -- solid_log_entries: Parsed and indexed log entries for querying
19
+ CREATE TABLE solid_log_entries (
20
+ id BIGSERIAL PRIMARY KEY,
21
+ raw_id BIGINT NOT NULL,
22
+ timestamp TIMESTAMP NOT NULL,
23
+ created_at TIMESTAMP NOT NULL,
24
+ level VARCHAR(255) NOT NULL,
25
+ app VARCHAR(255),
26
+ env VARCHAR(255),
27
+ message TEXT,
28
+ request_id VARCHAR(255),
29
+ job_id VARCHAR(255),
30
+ duration DOUBLE PRECISION,
31
+ status_code INTEGER,
32
+ controller VARCHAR(255),
33
+ action VARCHAR(255),
34
+ path VARCHAR(255),
35
+ method VARCHAR(255),
36
+ extra_fields TEXT,
37
+ fts_vector TSVECTOR
38
+ );
39
+
40
+ CREATE INDEX idx_entries_timestamp ON solid_log_entries(timestamp DESC);
41
+ CREATE INDEX idx_entries_level ON solid_log_entries(level);
42
+ CREATE INDEX idx_entries_app_env_time ON solid_log_entries(app, env, timestamp DESC);
43
+ CREATE INDEX idx_entries_request ON solid_log_entries(request_id);
44
+ CREATE INDEX idx_entries_job ON solid_log_entries(job_id);
45
+ CREATE INDEX idx_entries_raw ON solid_log_entries(raw_id);
46
+
47
+ -- Full-text search index using tsvector and GIN
48
+ CREATE INDEX idx_entries_fts ON solid_log_entries USING GIN(fts_vector);
49
+
50
+ -- Trigger to auto-update FTS vector
51
+ CREATE OR REPLACE FUNCTION solid_log_entries_fts_trigger() RETURNS trigger AS $$
52
+ BEGIN
53
+ NEW.fts_vector :=
54
+ setweight(to_tsvector('english', COALESCE(NEW.message, '')), 'A') ||
55
+ setweight(to_tsvector('english', COALESCE(NEW.extra_fields, '')), 'B');
56
+ RETURN NEW;
57
+ END;
58
+ $$ LANGUAGE plpgsql;
59
+
60
+ CREATE TRIGGER solid_log_entries_fts_update
61
+ BEFORE INSERT OR UPDATE ON solid_log_entries
62
+ FOR EACH ROW EXECUTE FUNCTION solid_log_entries_fts_trigger();
63
+
64
+ -- solid_log_fields: Field registry for tracking dynamic JSON fields
65
+ CREATE TABLE solid_log_fields (
66
+ id BIGSERIAL PRIMARY KEY,
67
+ name VARCHAR(255) NOT NULL,
68
+ field_type VARCHAR(255) NOT NULL,
69
+ filter_type VARCHAR(255) NOT NULL DEFAULT 'multiselect',
70
+ usage_count INTEGER NOT NULL DEFAULT 0,
71
+ last_seen_at TIMESTAMP,
72
+ promoted BOOLEAN NOT NULL DEFAULT FALSE,
73
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
74
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
75
+ );
76
+
77
+ CREATE UNIQUE INDEX idx_fields_name ON solid_log_fields(name);
78
+ CREATE INDEX idx_fields_promoted ON solid_log_fields(promoted);
79
+ CREATE INDEX idx_fields_usage ON solid_log_fields(usage_count);
80
+
81
+ -- solid_log_tokens: API tokens for log ingestion authentication
82
+ CREATE TABLE solid_log_tokens (
83
+ id BIGSERIAL PRIMARY KEY,
84
+ name VARCHAR(255) NOT NULL,
85
+ token_hash VARCHAR(255) NOT NULL,
86
+ last_used_at TIMESTAMP,
87
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
88
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
89
+ );
90
+
91
+ CREATE UNIQUE INDEX idx_tokens_hash ON solid_log_tokens(token_hash);
92
+ CREATE INDEX idx_tokens_name ON solid_log_tokens(name);
93
+
94
+ -- solid_log_facet_cache: Cache for filter options to reduce DB load
95
+ CREATE TABLE solid_log_facet_cache (
96
+ id BIGSERIAL PRIMARY KEY,
97
+ key_name VARCHAR(255) NOT NULL,
98
+ cache_value TEXT NOT NULL,
99
+ expires_at TIMESTAMP,
100
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
101
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
102
+ );
103
+
104
+ CREATE UNIQUE INDEX idx_facet_key_name ON solid_log_facet_cache(key_name);
105
+ CREATE INDEX idx_facet_expires ON solid_log_facet_cache(expires_at);
106
+
107
+ -- Schema migrations
108
+ CREATE TABLE IF NOT EXISTS schema_migrations (
109
+ version VARCHAR(255) PRIMARY KEY
110
+ );
111
+
112
+ INSERT INTO schema_migrations (version) VALUES
113
+ ('20251222000001'),
114
+ ('20251222000002'),
115
+ ('20251222000004'),
116
+ ('20251222000005'),
117
+ ('20251222000006'),
118
+ ('20251222000007');
@@ -0,0 +1,123 @@
1
+ -- SolidLog Database Structure for SQLite
2
+ -- Generated from migrations in db/log_migrate/
3
+
4
+ -- solid_log_raw: Fast ingestion table for raw log payloads
5
+ CREATE TABLE solid_log_raw (
6
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
7
+ received_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
8
+ token_id INTEGER NOT NULL,
9
+ payload TEXT NOT NULL,
10
+ parsed BOOLEAN NOT NULL DEFAULT 0,
11
+ parsed_at DATETIME
12
+ );
13
+
14
+ CREATE INDEX idx_raw_unparsed ON solid_log_raw(parsed, received_at);
15
+ CREATE INDEX idx_raw_token ON solid_log_raw(token_id);
16
+ CREATE INDEX idx_raw_received ON solid_log_raw(received_at);
17
+
18
+ -- solid_log_entries: Parsed and indexed log entries for querying
19
+ CREATE TABLE solid_log_entries (
20
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
21
+ raw_id INTEGER NOT NULL,
22
+ timestamp DATETIME NOT NULL,
23
+ created_at DATETIME NOT NULL,
24
+ level VARCHAR(255) NOT NULL,
25
+ app VARCHAR(255),
26
+ env VARCHAR(255),
27
+ message TEXT,
28
+ request_id VARCHAR(255),
29
+ job_id VARCHAR(255),
30
+ duration REAL,
31
+ status_code INTEGER,
32
+ controller VARCHAR(255),
33
+ action VARCHAR(255),
34
+ path VARCHAR(255),
35
+ method VARCHAR(255),
36
+ extra_fields TEXT
37
+ );
38
+
39
+ CREATE INDEX idx_entries_timestamp ON solid_log_entries(timestamp DESC);
40
+ CREATE INDEX idx_entries_level ON solid_log_entries(level);
41
+ CREATE INDEX idx_entries_app_env_time ON solid_log_entries(app, env, timestamp DESC);
42
+ CREATE INDEX idx_entries_request ON solid_log_entries(request_id);
43
+ CREATE INDEX idx_entries_job ON solid_log_entries(job_id);
44
+ CREATE INDEX idx_entries_raw ON solid_log_entries(raw_id);
45
+
46
+ -- solid_log_fields: Field registry for tracking dynamic JSON fields
47
+ CREATE TABLE solid_log_fields (
48
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
49
+ name VARCHAR(255) NOT NULL,
50
+ field_type VARCHAR(255) NOT NULL,
51
+ filter_type VARCHAR(255) NOT NULL DEFAULT 'multiselect',
52
+ usage_count INTEGER NOT NULL DEFAULT 0,
53
+ last_seen_at DATETIME,
54
+ promoted BOOLEAN NOT NULL DEFAULT 0,
55
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
56
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
57
+ );
58
+
59
+ CREATE UNIQUE INDEX idx_fields_name ON solid_log_fields(name);
60
+ CREATE INDEX idx_fields_promoted ON solid_log_fields(promoted);
61
+ CREATE INDEX idx_fields_usage ON solid_log_fields(usage_count);
62
+
63
+ -- solid_log_tokens: API tokens for log ingestion authentication
64
+ CREATE TABLE solid_log_tokens (
65
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
66
+ name VARCHAR(255) NOT NULL,
67
+ token_hash VARCHAR(255) NOT NULL,
68
+ last_used_at DATETIME,
69
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
70
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
71
+ );
72
+
73
+ CREATE UNIQUE INDEX idx_tokens_hash ON solid_log_tokens(token_hash);
74
+ CREATE INDEX idx_tokens_name ON solid_log_tokens(name);
75
+
76
+ -- solid_log_facet_cache: Cache for filter options to reduce DB load
77
+ CREATE TABLE solid_log_facet_cache (
78
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
79
+ key_name VARCHAR(255) NOT NULL,
80
+ cache_value TEXT NOT NULL,
81
+ expires_at DATETIME,
82
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
83
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
84
+ );
85
+
86
+ CREATE UNIQUE INDEX idx_facet_key_name ON solid_log_facet_cache(key_name);
87
+ CREATE INDEX idx_facet_expires ON solid_log_facet_cache(expires_at);
88
+
89
+ -- Full-Text Search (SQLite FTS5)
90
+ CREATE VIRTUAL TABLE solid_log_entries_fts USING fts5(
91
+ message,
92
+ extra_fields,
93
+ content='solid_log_entries',
94
+ content_rowid='id'
95
+ );
96
+
97
+ CREATE TRIGGER solid_log_entries_fts_insert AFTER INSERT ON solid_log_entries BEGIN
98
+ INSERT INTO solid_log_entries_fts(rowid, message, extra_fields)
99
+ VALUES (new.id, new.message, new.extra_fields);
100
+ END;
101
+
102
+ CREATE TRIGGER solid_log_entries_fts_update AFTER UPDATE ON solid_log_entries BEGIN
103
+ UPDATE solid_log_entries_fts
104
+ SET message = new.message, extra_fields = new.extra_fields
105
+ WHERE rowid = new.id;
106
+ END;
107
+
108
+ CREATE TRIGGER solid_log_entries_fts_delete AFTER DELETE ON solid_log_entries BEGIN
109
+ DELETE FROM solid_log_entries_fts WHERE rowid = old.id;
110
+ END;
111
+
112
+ -- Schema migrations
113
+ CREATE TABLE IF NOT EXISTS schema_migrations (
114
+ version VARCHAR(255) PRIMARY KEY
115
+ );
116
+
117
+ INSERT INTO schema_migrations (version) VALUES
118
+ ('20251222000001'),
119
+ ('20251222000002'),
120
+ ('20251222000004'),
121
+ ('20251222000005'),
122
+ ('20251222000006'),
123
+ ('20251222000007');