solid_log-service 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.
@@ -0,0 +1,130 @@
1
+ require "action_cable" if defined?(Rails)
2
+
3
+ module SolidLog
4
+ class ParserJob < ApplicationJob
5
+ queue_as :default
6
+
7
+ # Process a batch of unparsed raw entries
8
+ def perform(batch_size: nil)
9
+ batch_size ||= SolidLog.configuration.parser_batch_size
10
+
11
+ SolidLog.without_logging do
12
+ # Claim a batch of unparsed entries
13
+ raw_entries = RawEntry.claim_batch(batch_size: batch_size)
14
+
15
+ return if raw_entries.empty?
16
+
17
+ Rails.logger.info "SolidLog::ParserJob: Processing #{raw_entries.size} raw entries"
18
+
19
+ # Process each entry
20
+ entries_to_insert = []
21
+ fields_to_track = {}
22
+
23
+ raw_entries.each do |raw_entry|
24
+ begin
25
+ # Parse the raw payload
26
+ parsed = Parser.parse(raw_entry.payload)
27
+
28
+ # Extract dynamic fields for field registry
29
+ extra_fields = parsed.delete(:extra_fields) || {}
30
+ track_fields(fields_to_track, extra_fields)
31
+
32
+ # Prepare entry for insertion
33
+ entry_data = {
34
+ raw_id: raw_entry.id,
35
+ timestamp: parsed[:timestamp],
36
+ created_at: Time.current, # When entry was parsed/created
37
+ level: parsed[:level],
38
+ app: parsed[:app],
39
+ env: parsed[:env],
40
+ message: parsed[:message],
41
+ request_id: parsed[:request_id],
42
+ job_id: parsed[:job_id],
43
+ duration: parsed[:duration],
44
+ status_code: parsed[:status_code],
45
+ controller: parsed[:controller],
46
+ action: parsed[:action],
47
+ path: parsed[:path],
48
+ method: parsed[:method],
49
+ extra_fields: extra_fields.to_json
50
+ }
51
+
52
+ entries_to_insert << entry_data
53
+ rescue StandardError => e
54
+ Rails.logger.error "SolidLog::ParserJob: Failed to parse entry #{raw_entry.id}: #{e.message}"
55
+ Rails.logger.error e.backtrace.join("\n")
56
+ # Leave entry unparsed so it can be retried or investigated
57
+ end
58
+ end
59
+
60
+ # Bulk insert parsed entries
61
+ if entries_to_insert.any?
62
+ raw_ids = entries_to_insert.map { |e| e[:raw_id] }
63
+
64
+ Entry.insert_all(entries_to_insert)
65
+ Rails.logger.info "SolidLog::ParserJob: Inserted #{entries_to_insert.size} entries"
66
+
67
+ # Broadcast new entry IDs for live tail
68
+ begin
69
+ new_entry_ids = Entry.where(raw_id: raw_ids).pluck(:id)
70
+ if new_entry_ids.any?
71
+ ActionCable.server.broadcast(
72
+ "solid_log_new_entries",
73
+ { entry_ids: new_entry_ids }
74
+ )
75
+ end
76
+ rescue NameError => e
77
+ Rails.logger.error "SolidLog::ParserJob: ActionCable not available: #{e.message}"
78
+ rescue => e
79
+ Rails.logger.error "SolidLog::ParserJob: Failed to broadcast: #{e.class} - #{e.message}"
80
+ end
81
+ end
82
+
83
+ # Update field registry
84
+ update_field_registry(fields_to_track)
85
+ end
86
+ end
87
+
88
+ private
89
+
90
+ # Track field occurrences for the registry
91
+ def track_fields(fields_hash, extra_fields)
92
+ extra_fields.each do |key, value|
93
+ fields_hash[key] ||= { values: [], count: 0 }
94
+ fields_hash[key][:count] += 1
95
+ fields_hash[key][:type] ||= infer_field_type(value)
96
+ end
97
+ end
98
+
99
+ # Update the field registry with tracked fields
100
+ def update_field_registry(fields_hash)
101
+ fields_hash.each do |name, data|
102
+ field = Field.find_or_initialize_by(name: name)
103
+ field.field_type ||= data[:type]
104
+ field.usage_count += data[:count]
105
+ field.last_seen_at = Time.current
106
+ field.save!
107
+ end
108
+ end
109
+
110
+ # Infer field type from value
111
+ def infer_field_type(value)
112
+ case value
113
+ when String
114
+ "string"
115
+ when Numeric
116
+ "number"
117
+ when TrueClass, FalseClass
118
+ "boolean"
119
+ when Time, DateTime, Date
120
+ "datetime"
121
+ when Array
122
+ "array"
123
+ when Hash
124
+ "object"
125
+ else
126
+ "string"
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,24 @@
1
+ module SolidLog
2
+ class RetentionJob < ApplicationJob
3
+ queue_as :default
4
+
5
+ def perform(retention_days: 30, error_retention_days: 90, vacuum: false)
6
+ SolidLog.without_logging do
7
+ Rails.logger.info "SolidLog::RetentionJob: Starting cleanup (retention: #{retention_days} days, errors: #{error_retention_days} days)"
8
+
9
+ stats = RetentionService.cleanup(
10
+ retention_days: retention_days,
11
+ error_retention_days: error_retention_days
12
+ )
13
+
14
+ Rails.logger.info "SolidLog::RetentionJob: Deleted #{stats[:entries_deleted]} entries, #{stats[:raw_deleted]} raw entries, cleared #{stats[:cache_cleared]} cache entries"
15
+
16
+ if vacuum
17
+ Rails.logger.info "SolidLog::RetentionJob: Running VACUUM..."
18
+ RetentionService.vacuum_database
19
+ Rails.logger.info "SolidLog::RetentionJob: VACUUM complete"
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'rack'
5
+ require_relative '../lib/solid_log/service'
6
+
7
+ # Parse command line options
8
+ options = {
9
+ port: ENV['PORT'] || SolidLog::Service.configuration.port || 3001,
10
+ bind: ENV['BIND'] || SolidLog::Service.configuration.bind || '0.0.0.0',
11
+ environment: ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'production'
12
+ }
13
+
14
+ ARGV.each do |arg|
15
+ case arg
16
+ when /^--port=(\d+)$/
17
+ options[:port] = $1.to_i
18
+ when /^--bind=(.+)$/
19
+ options[:bind] = $1
20
+ when /^--environment=(.+)$/
21
+ options[:environment] = $1
22
+ when '--help', '-h'
23
+ puts <<~HELP
24
+ SolidLog Service - Standalone log ingestion and processing service
25
+
26
+ Usage: solid_log_service [options]
27
+
28
+ Options:
29
+ --port=PORT Port to bind to (default: 3001)
30
+ --bind=ADDRESS Address to bind to (default: 0.0.0.0)
31
+ --environment=ENV Environment (default: production)
32
+ --help, -h Show this help message
33
+
34
+ Environment Variables:
35
+ PORT Port to bind to
36
+ BIND Address to bind to
37
+ RAILS_ENV Rails environment
38
+ DATABASE_URL Database connection string
39
+ LOG_LEVEL Log level (debug, info, warn, error)
40
+
41
+ Examples:
42
+ solid_log_service
43
+ solid_log_service --port=8080 --bind=127.0.0.1
44
+ PORT=8080 solid_log_service
45
+
46
+ Configuration:
47
+ Create config/solid_log_service.rb to configure the service.
48
+ See README.md for configuration options.
49
+ HELP
50
+ exit 0
51
+ end
52
+ end
53
+
54
+ # Set environment
55
+ ENV['RAILS_ENV'] = options[:environment]
56
+ ENV['RACK_ENV'] = options[:environment]
57
+
58
+ puts "Starting SolidLog Service..."
59
+ puts " Environment: #{options[:environment]}"
60
+ puts " Binding: #{options[:bind]}:#{options[:port]}"
61
+
62
+ # Load the application
63
+ require_relative '../config.ru'
64
+
65
+ # Run with Puma
66
+ require 'puma/cli'
67
+
68
+ puma_args = [
69
+ '--bind', "tcp://#{options[:bind]}:#{options[:port]}",
70
+ '--environment', options[:environment],
71
+ '--workers', ENV.fetch('WEB_CONCURRENCY', '2'),
72
+ '--threads', "#{ENV.fetch('RAILS_MIN_THREADS', '5')}:#{ENV.fetch('RAILS_MAX_THREADS', '5')}"
73
+ ]
74
+
75
+ Puma::CLI.new(puma_args).run
data/config/cable.yml ADDED
@@ -0,0 +1,11 @@
1
+ development:
2
+ adapter: async
3
+
4
+ test:
5
+ adapter: test
6
+
7
+ production:
8
+ adapter: solid_cable
9
+ connects_to:
10
+ database:
11
+ writing: cable
data/config/routes.rb ADDED
@@ -0,0 +1,26 @@
1
+ SolidLog::Service::Engine.routes.draw do
2
+ namespace :api do
3
+ namespace :v1 do
4
+ # Ingestion
5
+ post "ingest", to: "ingest#create"
6
+
7
+ # Queries
8
+ resources :entries, only: [:index, :show]
9
+ post "search", to: "search#create"
10
+
11
+ # Facets
12
+ get "facets", to: "facets#index"
13
+ get "facets/all", to: "facets#all", as: :all_facets
14
+
15
+ # Timelines
16
+ get "timelines/request/:request_id", to: "timelines#show_request", as: :timeline_request
17
+ get "timelines/job/:job_id", to: "timelines#show_job", as: :timeline_job
18
+
19
+ # Health
20
+ get "health", to: "health#show"
21
+ end
22
+ end
23
+
24
+ # Root health check
25
+ get "/health", to: "api/v1/health#show"
26
+ end
data/config.ru ADDED
@@ -0,0 +1,7 @@
1
+ require_relative 'lib/solid_log/service'
2
+ require_relative 'lib/solid_log/service/application'
3
+
4
+ # Load routes
5
+ require_relative 'config/routes'
6
+
7
+ run SolidLog::Service::Application
@@ -0,0 +1,72 @@
1
+ require "rails"
2
+ require "action_controller/railtie"
3
+ require "active_record/railtie"
4
+ require "active_job/railtie"
5
+ require "action_cable/engine"
6
+
7
+ module SolidLog
8
+ module Service
9
+ class Application < Rails::Application
10
+ config.load_defaults 8.0
11
+ config.api_only = true
12
+
13
+ # Enable caching with memory store
14
+ config.cache_store = :memory_store
15
+
16
+ # Load service configuration
17
+ config.before_initialize do
18
+ # Load configuration file if it exists
19
+ config_file = Rails.root.join("config", "solid_log_service.rb")
20
+ require config_file if File.exist?(config_file)
21
+
22
+ # Load Action Cable configuration
23
+ cable_config_path = Rails.root.join("config", "cable.yml")
24
+ if File.exist?(cable_config_path)
25
+ config.action_cable.cable = YAML.load_file(cable_config_path, aliases: true)[Rails.env]
26
+ end
27
+ end
28
+
29
+ # Set up database connection
30
+ config.before_initialize do
31
+ db_config = {
32
+ adapter: ENV["DB_ADAPTER"] || "sqlite3",
33
+ database: ENV["DATABASE_URL"] || Rails.root.join("storage", "production_log.sqlite3").to_s,
34
+ pool: ENV.fetch("RAILS_MAX_THREADS", 5)
35
+ }
36
+
37
+ ActiveRecord::Base.establish_connection(db_config)
38
+ end
39
+
40
+ # Start job processor after initialization (but not in console mode)
41
+ config.after_initialize do
42
+ unless defined?(Rails::Console)
43
+ SolidLog::Service.start!
44
+ end
45
+ end
46
+
47
+ # Stop job processor on shutdown
48
+ at_exit do
49
+ SolidLog::Service.stop!
50
+ end
51
+
52
+ # Eager load controllers and jobs
53
+ config.eager_load_paths << Rails.root.join("app", "controllers")
54
+ config.eager_load_paths << Rails.root.join("app", "jobs")
55
+
56
+ # CORS configuration
57
+ config.middleware.insert_before 0, Rack::Cors do
58
+ allow do
59
+ origins { |source, env| SolidLog::Service.configuration.cors_origins.include?(source) || SolidLog::Service.configuration.cors_origins.include?("*") }
60
+ resource "*",
61
+ headers: :any,
62
+ methods: [:get, :post, :put, :patch, :delete, :options, :head],
63
+ credentials: false
64
+ end
65
+ end if defined?(Rack::Cors)
66
+
67
+ # Logging
68
+ config.logger = ActiveSupport::Logger.new(STDOUT)
69
+ config.log_level = ENV.fetch("LOG_LEVEL", "info").to_sym
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,84 @@
1
+ require "solid_log/core"
2
+
3
+ module SolidLog
4
+ module Service
5
+ class Configuration < SolidLog::Core::Configuration
6
+ attr_accessor :job_mode,
7
+ :parser_interval,
8
+ :cache_cleanup_interval,
9
+ :retention_hour,
10
+ :field_analysis_hour,
11
+ :websocket_enabled,
12
+ :cors_origins,
13
+ :bind,
14
+ :port
15
+
16
+ def initialize
17
+ super
18
+
19
+ # Load from ENV vars with defaults
20
+ # Job processing mode: :scheduler (default), :active_job, or :manual
21
+ @job_mode = env_to_symbol("SOLIDLOG_JOB_MODE", :scheduler)
22
+
23
+ # Scheduler intervals (only used when job_mode = :scheduler)
24
+ @parser_interval = env_to_int("SOLIDLOG_PARSER_INTERVAL", 10) # seconds
25
+ @cache_cleanup_interval = env_to_int("SOLIDLOG_CACHE_CLEANUP_INTERVAL", 3600) # seconds (1 hour)
26
+ @retention_hour = env_to_int("SOLIDLOG_RETENTION_HOUR", 2) # Hour of day (0-23)
27
+ @field_analysis_hour = env_to_int("SOLIDLOG_FIELD_ANALYSIS_HOUR", 3) # Hour of day (0-23)
28
+
29
+ # WebSocket support for live tail
30
+ @websocket_enabled = env_to_bool("SOLIDLOG_WEBSOCKET_ENABLED", false)
31
+
32
+ # CORS configuration for API
33
+ @cors_origins = env_to_array("SOLIDLOG_CORS_ORIGINS", [])
34
+
35
+ # Server configuration
36
+ @bind = ENV["SOLIDLOG_BIND"] || ENV["BIND"] || "0.0.0.0"
37
+ @port = env_to_int("SOLIDLOG_PORT") || env_to_int("PORT", 3001)
38
+ end
39
+
40
+ # Validate configuration
41
+ def valid?
42
+ errors = []
43
+
44
+ errors << "job_mode must be :scheduler, :active_job, or :manual" unless [:scheduler, :active_job, :manual].include?(job_mode)
45
+ errors << "parser_interval must be positive" unless parser_interval&.positive?
46
+ errors << "cache_cleanup_interval must be positive" unless cache_cleanup_interval&.positive?
47
+ errors << "retention_hour must be between 0 and 23" unless retention_hour&.between?(0, 23)
48
+ errors << "field_analysis_hour must be between 0 and 23" unless field_analysis_hour&.between?(0, 23)
49
+
50
+ if errors.any?
51
+ raise ArgumentError, "Invalid configuration:\n #{errors.join("\n ")}"
52
+ end
53
+
54
+ true
55
+ end
56
+
57
+ private
58
+
59
+ def env_to_int(key, default = nil)
60
+ value = ENV[key]
61
+ return default if value.nil? || value.empty?
62
+ value.to_i
63
+ end
64
+
65
+ def env_to_bool(key, default = false)
66
+ value = ENV[key]
67
+ return default if value.nil? || value.empty?
68
+ ["true", "1", "yes", "on"].include?(value.downcase)
69
+ end
70
+
71
+ def env_to_symbol(key, default = nil)
72
+ value = ENV[key]
73
+ return default if value.nil? || value.empty?
74
+ value.to_sym
75
+ end
76
+
77
+ def env_to_array(key, default = [])
78
+ value = ENV[key]
79
+ return default if value.nil? || value.empty?
80
+ value.split(",").map(&:strip)
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,25 @@
1
+ require "rails/engine"
2
+
3
+ module SolidLog
4
+ module Service
5
+ class Engine < ::Rails::Engine
6
+ isolate_namespace SolidLog
7
+
8
+ config.generators do |g|
9
+ g.test_framework :minitest
10
+ g.fixture_replacement :factory_bot
11
+ end
12
+
13
+ # Ensure controllers and jobs are autoloaded
14
+ config.autoload_paths << root.join("app/controllers")
15
+ config.autoload_paths << root.join("app/jobs")
16
+
17
+ # Add SilenceMiddleware to prevent recursive logging
18
+ # This intercepts all requests to the service and sets Thread.current[:solid_log_silenced]
19
+ # so the service doesn't log its own API requests, parser jobs, etc.
20
+ initializer "solid_log_service.add_middleware" do |app|
21
+ app.middleware.use SolidLog::SilenceMiddleware
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,82 @@
1
+ module SolidLog
2
+ module Service
3
+ class JobProcessor
4
+ class << self
5
+ attr_reader :scheduler
6
+
7
+ def setup
8
+ case configuration.job_mode
9
+ when :scheduler
10
+ setup_scheduler
11
+ when :active_job
12
+ setup_active_job
13
+ when :manual
14
+ setup_manual
15
+ else
16
+ raise ArgumentError, "Invalid job_mode: #{configuration.job_mode}. Must be :scheduler, :active_job, or :manual"
17
+ end
18
+ end
19
+
20
+ def stop
21
+ case configuration.job_mode
22
+ when :scheduler
23
+ stop_scheduler
24
+ when :active_job, :manual
25
+ # Nothing to stop
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def configuration
32
+ SolidLog::Service.configuration
33
+ end
34
+
35
+ def setup_scheduler
36
+ @scheduler = Scheduler.new(configuration)
37
+ @scheduler.start
38
+
39
+ Rails.logger.info "SolidLog::Service: Started built-in Scheduler"
40
+ Rails.logger.info " Parser interval: #{configuration.parser_interval}s"
41
+ Rails.logger.info " Cache cleanup interval: #{configuration.cache_cleanup_interval}s"
42
+ Rails.logger.info " Retention hour: #{configuration.retention_hour}:00"
43
+ Rails.logger.info " Field analysis hour: #{configuration.field_analysis_hour}:00"
44
+ end
45
+
46
+ def stop_scheduler
47
+ if @scheduler
48
+ @scheduler.stop
49
+ @scheduler = nil
50
+ end
51
+ end
52
+
53
+ def setup_active_job
54
+ # Jobs are enqueued via host app's ActiveJob backend
55
+ # Host app should configure recurring jobs using their job backend
56
+ # Example with Solid Queue:
57
+ #
58
+ # SolidQueue::RecurringTask.create!(
59
+ # key: 'solidlog_parser',
60
+ # schedule: 'every 10 seconds',
61
+ # class_name: 'SolidLog::ParserJob'
62
+ # )
63
+
64
+ Rails.logger.info "SolidLog::Service: Using ActiveJob for background processing"
65
+ Rails.logger.info " Make sure to configure recurring jobs in your host application"
66
+ end
67
+
68
+ def setup_manual
69
+ # User manages scheduling via cron or other external scheduler
70
+ # No setup needed
71
+
72
+ Rails.logger.info "SolidLog::Service: Manual job mode (no auto-scheduling)"
73
+ Rails.logger.info " Set up cron jobs to run:"
74
+ Rails.logger.info " - rails solid_log:parse_logs (every 10 seconds recommended)"
75
+ Rails.logger.info " - rails solid_log:cache_cleanup (hourly recommended)"
76
+ Rails.logger.info " - rails solid_log:retention (daily recommended)"
77
+ Rails.logger.info " - rails solid_log:field_analysis (daily recommended)"
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,146 @@
1
+ require "thread"
2
+
3
+ module SolidLog
4
+ module Service
5
+ class Scheduler
6
+ attr_reader :threads, :running
7
+
8
+ def initialize(config = SolidLog::Service.configuration)
9
+ @config = config
10
+ @threads = []
11
+ @running = false
12
+ @mutex = Mutex.new
13
+ end
14
+
15
+ def start
16
+ return if @running
17
+
18
+ @mutex.synchronize do
19
+ return if @running
20
+ @running = true
21
+ end
22
+
23
+ Rails.logger.info "SolidLog::Service::Scheduler starting..."
24
+
25
+ # Parser job - frequent (configurable, default 10s)
26
+ thread = Thread.new { parser_loop }
27
+ thread.abort_on_exception = true
28
+ @threads << thread
29
+
30
+ # Cache cleanup - configurable (default hourly)
31
+ thread = Thread.new { cache_cleanup_loop }
32
+ thread.abort_on_exception = true
33
+ @threads << thread
34
+
35
+ # Daily jobs - retention and field analysis
36
+ thread = Thread.new { daily_jobs_loop }
37
+ thread.abort_on_exception = true
38
+ @threads << thread
39
+
40
+ Rails.logger.info "SolidLog::Service::Scheduler started with #{@threads.size} threads"
41
+ end
42
+
43
+ def stop
44
+ return unless @running
45
+
46
+ Rails.logger.info "SolidLog::Service::Scheduler stopping..."
47
+
48
+ @mutex.synchronize do
49
+ @running = false
50
+ end
51
+
52
+ # Give threads 5 seconds to finish gracefully
53
+ @threads.each { |t| t.join(5) }
54
+
55
+ # Force kill any remaining threads
56
+ @threads.each { |t| t.kill if t.alive? }
57
+ @threads.clear
58
+
59
+ Rails.logger.info "SolidLog::Service::Scheduler stopped"
60
+ end
61
+
62
+ def running?
63
+ @running
64
+ end
65
+
66
+ private
67
+
68
+ def parser_loop
69
+ loop do
70
+ break unless @running
71
+
72
+ begin
73
+ SolidLog::ParserJob.perform_now
74
+ rescue => e
75
+ Rails.logger.error "SolidLog::Scheduler: Parser job failed: #{e.message}"
76
+ Rails.logger.error e.backtrace.join("\n")
77
+ end
78
+
79
+ sleep @config.parser_interval
80
+ end
81
+ end
82
+
83
+ def cache_cleanup_loop
84
+ loop do
85
+ break unless @running
86
+
87
+ begin
88
+ SolidLog::CacheCleanupJob.perform_now
89
+ rescue => e
90
+ Rails.logger.error "SolidLog::Scheduler: Cache cleanup failed: #{e.message}"
91
+ Rails.logger.error e.backtrace.join("\n")
92
+ end
93
+
94
+ sleep @config.cache_cleanup_interval
95
+ end
96
+ end
97
+
98
+ def daily_jobs_loop
99
+ loop do
100
+ break unless @running
101
+
102
+ current_hour = Time.current.hour
103
+
104
+ # Run retention job at configured hour (default 2 AM)
105
+ if current_hour == @config.retention_hour
106
+ run_daily_job(:retention)
107
+ sleep 1.hour # Wait an hour to avoid running multiple times
108
+ end
109
+
110
+ # Run field analysis at configured hour (default 3 AM)
111
+ if current_hour == @config.field_analysis_hour
112
+ run_daily_job(:field_analysis)
113
+ sleep 1.hour # Wait an hour to avoid running multiple times
114
+ end
115
+
116
+ # Check every 10 minutes
117
+ sleep 10.minutes
118
+ end
119
+ end
120
+
121
+ def run_daily_job(job_name)
122
+ case job_name
123
+ when :retention
124
+ begin
125
+ SolidLog::RetentionJob.perform_now(
126
+ retention_days: @config.retention_days,
127
+ error_retention_days: @config.error_retention_days
128
+ )
129
+ rescue => e
130
+ Rails.logger.error "SolidLog::Scheduler: Retention job failed: #{e.message}"
131
+ Rails.logger.error e.backtrace.join("\n")
132
+ end
133
+ when :field_analysis
134
+ begin
135
+ SolidLog::FieldAnalysisJob.perform_now(
136
+ auto_promote: @config.auto_promote_fields
137
+ )
138
+ rescue => e
139
+ Rails.logger.error "SolidLog::Scheduler: Field analysis failed: #{e.message}"
140
+ Rails.logger.error e.backtrace.join("\n")
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,5 @@
1
+ module SolidLog
2
+ module Service
3
+ VERSION = "0.1.0"
4
+ end
5
+ end