logdash 1.0.2 → 1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 69847fc748022726228d0cd3ada597929dce0388cb395983a2c5dcd7132434e3
4
- data.tar.gz: 710765c7b4f059ac7510d14c1888ae4925cb06511c85b826e674c40d40fccad5
3
+ metadata.gz: 0c20102d3b1fbe508e7520c30fcf14e87eb7cb07e315bc90972b45a6d4d6b873
4
+ data.tar.gz: 32dc2ed4ef2bfbbd122c4da789cb68bdde85ba32fbb10400fec6a92853bbcde1
5
5
  SHA512:
6
- metadata.gz: e203631a07366cf56f3eb33ba00e72234302ed50799e2b3041bf07e4884c0562efd8712b33690881a5c2d26057d921abb7e1445e3a7bfd3d08b419591688d1cb
7
- data.tar.gz: 1fb7dbc5f8f7aa331a8f3f659f2e1703a717ca91e4420ce7c8fc40c5eab684621c259dbde54762d1868b5f4979b701b7ffa55aa0796ea77a8b34254bc87d6996
6
+ metadata.gz: 43c5dad64201cda45a6411302aeca58dab55c739d33fb525b56453b5c3a4b10ba2df707f18914df4ebb9586d23fd75531f4598d9506a3cc82365583f94063fc3
7
+ data.tar.gz: 6597038b2ea87972c0de8b95a3c4cd8e6df5a7fb1ca07ce759c1cb4b0a4340aa85320ebc7f77b0d6b0c67f2dbd07ce3ff6c8295b34b826351a492550c2404a2e
data/README.md CHANGED
@@ -49,12 +49,31 @@ logger.silly('Silly level logging')
49
49
  require 'logdash'
50
50
 
51
51
  logdash_client = Logdash.create(api_key: "YOUR_API_KEY")
52
- metrics = $logdash_client[:metrics]
52
+ metrics = logdash_client[:metrics]
53
53
 
54
54
  metrics.set('users', 0)
55
55
  metrics.mutate('users', 1)
56
56
  ```
57
57
 
58
+ ## Events
59
+
60
+ ```ruby
61
+ require 'logdash'
62
+
63
+ logdash_client = Logdash.create(api_key: "YOUR_API_KEY")
64
+ events = logdash_client[:events]
65
+
66
+ events.track('agent_run', { agent: 'support', steps: 4, success: true }, user_id: current_user.id)
67
+ ```
68
+
69
+ Events show up in your domain's Analytics as goals, with a daily trend.
70
+ Pass `user_id` for signed-in users, so one person counts once across your website and apps.
71
+ Without it, Logdash counts a visitor of that app that changes every day.
72
+ Never put personal data in props.
73
+
74
+ Events are sent in the background, and a script waits for them before it exits.
75
+ Call `events.flush` to wait for them sooner, for example before a serverless function returns.
76
+
58
77
  ## Ruby on Rails
59
78
 
60
79
  In a Ruby on Rails application, you can use an initializer to configure Logdash. Create a new file in `config/initializers/logdash.rb` with the following content:
@@ -66,18 +85,32 @@ ActiveSupport.on_load(:after_initialize) do
66
85
  $logdash_client = Logdash.create(api_key: "YOUR_API_KEY")
67
86
  $logger = $logdash_client[:logger]
68
87
  $metrics = $logdash_client[:metrics]
88
+ $events = $logdash_client[:events]
69
89
  end
70
90
  ```
71
91
 
72
- This will make `$logger` and `$metrics` available throughout your Ruby on Rails application.
92
+ This will make `$logger`, `$metrics` and `$events` available throughout your Ruby on Rails application.
73
93
 
74
94
  ## Configuration
75
95
 
76
- | Parameter | Required | Default | Description |
77
- | --------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------ |
78
- | `api_key` | no | - | Api key used to authorize against logdash servers. If you don't provide one, logs will be logged into local console only |
79
- | `host` | no | `https://api.logdash.io` | Custom API host, useful with self-hosted instances |
80
- | `verbose` | no | `false` | Useful for debugging purposes |
96
+ | Parameter | Required | Default | Description |
97
+ | --------- | -------- | ------------------------ | --------------------------------------------------------------------------------------------------------------- |
98
+ | `api_key` | no | - | API key used to authorize against Logdash servers. Without one, logs and events print to the local console only |
99
+ | `host` | no | `https://api.logdash.io` | Custom API host, useful with self-hosted instances |
100
+ | `verbose` | no | `false` | Useful for debugging purposes |
101
+ | `app` | no | - | Name of the app sending logs, metrics and events. See [Apps](#apps) |
102
+
103
+ ### Apps
104
+
105
+ A domain key is shared by all apps of a domain, for example a desktop app, an API and a landing page.
106
+ `app` names the app this client runs in, so the dashboard can filter logs, metrics and events by app:
107
+
108
+ ```ruby
109
+ logdash_client = Logdash.create(api_key: "YOUR_DOMAIN_KEY", app: "api")
110
+ ```
111
+
112
+ Logdash creates the app on first use.
113
+ Older service keys ignore `app`.
81
114
 
82
115
  ## View
83
116
 
@@ -3,41 +3,44 @@
3
3
  require_relative 'logger'
4
4
  require_relative 'metrics'
5
5
  require_relative 'http_log_sync'
6
+ require_relative 'events'
6
7
 
7
8
  module Logdash
8
9
  class Client
9
- attr_reader :logger, :metrics
10
+ attr_reader :logger, :metrics, :events
10
11
 
11
- def initialize(api_key: nil, host: 'https://api.logdash.io', verbose: false)
12
+ def initialize(api_key: nil, host: 'https://api.logdash.io', verbose: false, app: nil)
12
13
  @api_key = api_key
13
14
  @host = host
14
15
  @verbose = verbose
16
+ @app = app
15
17
 
16
- log_sync_instance = create_log_sync(api_key, host, verbose)
18
+ log_sync_instance = create_log_sync(api_key, host, verbose, app)
17
19
  @logger = Logger.new(log_sync: log_sync_instance)
18
20
 
19
- @metrics = create_metrics(api_key, host, verbose)
21
+ @metrics = create_metrics(api_key, host, verbose, app)
22
+ @events = Events.new(api_key: api_key, host: host, verbose: verbose, app: app)
20
23
  end
21
24
 
22
- def self.create(api_key: nil, host: 'https://api.logdash.io', verbose: false)
23
- client = new(api_key: api_key, host: host, verbose: verbose)
24
- { logger: client.logger, metrics: client.metrics }
25
+ def self.create(api_key: nil, host: 'https://api.logdash.io', verbose: false, app: nil)
26
+ client = new(api_key: api_key, host: host, verbose: verbose, app: app)
27
+ { logger: client.logger, metrics: client.metrics, events: client.events }
25
28
  end
26
29
 
27
30
  private
28
31
 
29
- def create_log_sync(api_key, host, verbose)
32
+ def create_log_sync(api_key, host, verbose, app)
30
33
  if api_key && !api_key.empty?
31
- HttpLogSync.new(api_key: api_key, host: host, verbose: verbose)
34
+ HttpLogSync.new(api_key: api_key, host: host, verbose: verbose, app: app)
32
35
  else
33
36
  puts '[LogDash] API key not provided. Using local logger only.' if verbose
34
37
  NoopLogSync.new
35
38
  end
36
39
  end
37
40
 
38
- def create_metrics(api_key, host, verbose)
41
+ def create_metrics(api_key, host, verbose, app)
39
42
  if api_key && !api_key.empty?
40
- Metrics.new(api_key: api_key, host: host, verbose: verbose)
43
+ Metrics.new(api_key: api_key, host: host, verbose: verbose, app: app)
41
44
  else
42
45
  puts '[LogDash] API key not provided. Metrics will not be registered.' if verbose
43
46
  NoopMetrics.new
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'uri'
5
+ require 'json'
6
+ require 'securerandom'
7
+ require_relative 'logger'
8
+
9
+ module Logdash
10
+ class Events
11
+ NAME = /\A[a-z][a-z0-9_]{0,63}\z/.freeze
12
+ WEBSITE_EVENTS = %w[pageview pageleave browser_error].freeze
13
+
14
+ # Ruby kills the threads still sending events when the main thread exits, so it waits for them first
15
+ SENDING = ThreadGroup.new
16
+ at_exit { SENDING.list.each(&:join) }
17
+
18
+ def initialize(api_key:, host:, verbose:, app: nil)
19
+ @api_key = api_key
20
+ @host = host
21
+ @verbose = verbose
22
+ @app = app
23
+ @console = Logger.new
24
+ end
25
+
26
+ # Ruby < 3 passes a braced props hash as keywords, so keywords other than user_id are props too
27
+ def track(name, props = {}, user_id: nil, **more_props)
28
+ name = name.to_s
29
+ user_id = user_id.to_s
30
+ validate(name, user_id)
31
+ props = props.to_h.merge(more_props)
32
+ return @console.info("Event #{name} #{props.to_json}") if @api_key.to_s.empty?
33
+
34
+ body = event_body(name, props, user_id)
35
+ SENDING.add(Thread.new { send_event(name, body) })
36
+ rescue StandardError => e
37
+ @console.warn("Dropping event #{name.inspect}: #{e.message}")
38
+ end
39
+
40
+ # Waits for the events still being sent, for example before a serverless function returns
41
+ def flush
42
+ SENDING.list.each(&:join)
43
+ end
44
+
45
+ private
46
+
47
+ def validate(name, user_id)
48
+ if !NAME.match?(name) || WEBSITE_EVENTS.include?(name)
49
+ raise ArgumentError, 'its name must match [a-z][a-z0-9_]{0,63} and not be pageview, pageleave or browser_error'
50
+ end
51
+ raise ArgumentError, 'user_id must be an internal ID, never an email address' if user_id.include?('@')
52
+ end
53
+
54
+ def event_body(name, props, user_id)
55
+ # The server stores an event once per id, so a retry must send this same body
56
+ body = { id: SecureRandom.uuid, name: name }
57
+ body[:props] = props unless props.empty?
58
+ body[:userId] = user_id unless user_id.empty?
59
+ body[:app] = @app if @app && !@app.empty?
60
+ body.to_json
61
+ end
62
+
63
+ def send_event(name, body)
64
+ uri = URI.parse("#{@host}/events")
65
+ http = Net::HTTP.new(uri.host, uri.port)
66
+ http.use_ssl = (uri.scheme == 'https')
67
+ http.open_timeout = 5
68
+ http.read_timeout = 5
69
+ response = http.post(uri.request_uri, body, 'Content-Type' => 'application/json', 'project-api-key' => @api_key)
70
+ puts "[LogDash BG] Sent event (#{name}), status: #{response.code}" if @verbose
71
+ rescue StandardError => e
72
+ puts "[LogDash BG] Error sending event (#{name}): #{e.message}" if @verbose
73
+ end
74
+ end
75
+ end
@@ -6,10 +6,11 @@ require 'json'
6
6
 
7
7
  module Logdash
8
8
  class HttpLogSync
9
- def initialize(api_key:, host:, verbose:)
9
+ def initialize(api_key:, host:, verbose:, app: nil)
10
10
  @api_key = api_key
11
11
  @host = host
12
12
  @verbose = verbose
13
+ @app = app
13
14
  @sequence_number = 0
14
15
  @sequence_mutex = Mutex.new
15
16
  end
@@ -52,12 +53,9 @@ module Logdash
52
53
  request = Net::HTTP::Post.new(uri.request_uri)
53
54
  request['Content-Type'] = 'application/json'
54
55
  request['project-api-key'] = @api_key
55
- request.body = {
56
- message: message,
57
- level: level,
58
- createdAt: created_at,
59
- sequenceNumber: sequence_number
60
- }.to_json
56
+ body = { message: message, level: level, createdAt: created_at, sequenceNumber: sequence_number }
57
+ body[:app] = @app if @app && !@app.empty?
58
+ request.body = body.to_json
61
59
  request
62
60
  end
63
61
 
@@ -23,10 +23,11 @@ module Logdash
23
23
  class Metrics
24
24
  include BaseMetrics
25
25
 
26
- def initialize(api_key:, host:, verbose:)
26
+ def initialize(api_key:, host:, verbose:, app: nil)
27
27
  @api_key = api_key
28
28
  @host = host
29
29
  @verbose = verbose
30
+ @app = app
30
31
  end
31
32
 
32
33
  def set(name, value)
@@ -51,16 +52,7 @@ module Logdash
51
52
  http.open_timeout = 5
52
53
  http.read_timeout = 5
53
54
 
54
- request = Net::HTTP::Put.new(uri.request_uri)
55
- request['Content-Type'] = 'application/json'
56
- request['project-api-key'] = @api_key
57
- request.body = {
58
- name: name,
59
- value: value,
60
- operation: operation
61
- }.to_json
62
-
63
- response = http.request(request)
55
+ response = http.request(build_request(uri, name, value, operation))
64
56
  puts "[LogDash BG] Sent metric (#{name}, op: #{operation}), status: #{response.code}" if @verbose
65
57
  rescue Net::OpenTimeout, Net::ReadTimeout => e
66
58
  puts "[LogDash BG] Timeout sending metric (#{name}): #{e.message}" if @verbose
@@ -68,6 +60,16 @@ module Logdash
68
60
  puts "[LogDash BG] Error sending metric (#{name}): #{e.message}" if @verbose
69
61
  end
70
62
  end
63
+
64
+ def build_request(uri, name, value, operation)
65
+ request = Net::HTTP::Put.new(uri.request_uri)
66
+ request['Content-Type'] = 'application/json'
67
+ request['project-api-key'] = @api_key
68
+ body = { name: name, value: value, operation: operation }
69
+ body[:app] = @app if @app && !@app.empty?
70
+ request.body = body.to_json
71
+ request
72
+ end
71
73
  end
72
74
 
73
75
  class NoopMetrics
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Logdash
4
- VERSION = '1.0.2'
4
+ # CI writes the released version here before it builds the gem; the v<version> tags are the source of truth
5
+ VERSION = '1.1.0'
5
6
  end
data/lib/logdash.rb CHANGED
@@ -5,11 +5,11 @@ require_relative 'logdash/version'
5
5
  require_relative 'logdash/logger'
6
6
 
7
7
  module Logdash
8
- def self.create(api_key: nil, host: 'https://api.logdash.io', verbose: false)
9
- Client.create(api_key: api_key, host: host, verbose: verbose)
8
+ def self.create(api_key: nil, host: 'https://api.logdash.io', verbose: false, app: nil)
9
+ Client.create(api_key: api_key, host: host, verbose: verbose, app: app)
10
10
  end
11
11
 
12
- def self.new_client(api_key: nil, host: 'https://api.logdash.io', verbose: false)
13
- Client.new(api_key: api_key, host: host, verbose: verbose)
12
+ def self.new_client(api_key: nil, host: 'https://api.logdash.io', verbose: false, app: nil)
13
+ Client.new(api_key: api_key, host: host, verbose: verbose, app: app)
14
14
  end
15
15
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logdash
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - krzysztoff1
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2025-05-22 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: json
@@ -86,14 +85,14 @@ dependencies:
86
85
  requirements:
87
86
  - - "~>"
88
87
  - !ruby/object:Gem::Version
89
- version: '2.25'
88
+ version: '3.0'
90
89
  type: :development
91
90
  prerelease: false
92
91
  version_requirements: !ruby/object:Gem::Requirement
93
92
  requirements:
94
93
  - - "~>"
95
94
  - !ruby/object:Gem::Version
96
- version: '2.25'
95
+ version: '3.0'
97
96
  - !ruby/object:Gem::Dependency
98
97
  name: simplecov
99
98
  requirement: !ruby/object:Gem::Requirement
@@ -143,12 +142,11 @@ executables: []
143
142
  extensions: []
144
143
  extra_rdoc_files: []
145
144
  files:
146
- - ".rubocop.yml"
147
- - CHANGELOG.md
148
145
  - LICENSE
149
146
  - README.md
150
147
  - lib/logdash.rb
151
148
  - lib/logdash/client.rb
149
+ - lib/logdash/events.rb
152
150
  - lib/logdash/http_log_sync.rb
153
151
  - lib/logdash/logger.rb
154
152
  - lib/logdash/metrics.rb
@@ -159,9 +157,8 @@ licenses:
159
157
  metadata:
160
158
  homepage_uri: https://logdash.io/
161
159
  source_code_uri: https://github.com/logdash-io/ruby-sdk
162
- changelog_uri: https://github.com/logdash-io/ruby-sdk/blob/main/CHANGELOG.md
160
+ changelog_uri: https://github.com/logdash-io/ruby-sdk/releases
163
161
  rubygems_mfa_required: 'true'
164
- post_install_message:
165
162
  rdoc_options: []
166
163
  require_paths:
167
164
  - lib
@@ -176,8 +173,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
176
173
  - !ruby/object:Gem::Version
177
174
  version: '0'
178
175
  requirements: []
179
- rubygems_version: 3.5.20
180
- signing_key:
176
+ rubygems_version: 3.6.9
181
177
  specification_version: 4
182
178
  summary: Ruby client for LogDash observability platform.
183
179
  test_files: []
data/.rubocop.yml DELETED
@@ -1,64 +0,0 @@
1
- require:
2
- - rubocop-rspec
3
-
4
- AllCops:
5
- NewCops: enable
6
- TargetRubyVersion: 2.6
7
- Exclude:
8
- - "bin/*"
9
- - "vendor/**/*"
10
- - "tmp/**/*"
11
- - "logdash-*.gem"
12
- SuggestExtensions: false
13
-
14
- Capybara:
15
- Enabled: false
16
-
17
- FactoryBot:
18
- Enabled: false
19
-
20
- Capybara/RSpec/PredicateMatcher:
21
- Enabled: false
22
-
23
- Gemspec/DevelopmentDependencies:
24
- Enabled: false
25
-
26
- Style/Documentation:
27
- Enabled: false
28
-
29
- Style/FrozenStringLiteralComment:
30
- Enabled: true
31
-
32
- Layout/LineLength:
33
- Max: 120
34
-
35
- Metrics/BlockLength:
36
- Exclude:
37
- - "spec/**/*"
38
- - "test/**/*"
39
- - "ruby-sdk.gemspec"
40
-
41
- Metrics/ModuleLength:
42
- Max: 150
43
-
44
- Metrics/ClassLength:
45
- Max: 150
46
-
47
- Metrics/CyclomaticComplexity:
48
- Max: 10
49
-
50
- Metrics/PerceivedComplexity:
51
- Max: 10
52
-
53
- Metrics/AbcSize:
54
- Max: 26
55
-
56
- RSpec/ExampleLength:
57
- Max: 10
58
-
59
- RSpec/MultipleMemoizedHelpers:
60
- Max: 10
61
-
62
- Metrics/MethodLength:
63
- Exclude:
64
- - "lib/logdash/metrics.rb"
data/CHANGELOG.md DELETED
@@ -1,18 +0,0 @@
1
- # Changelog
2
-
3
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
4
-
5
- ## [1.0.2] - 2025-05-22
6
-
7
- ### Fixed
8
- - Ensure `createdAt` parameter is sent correctly in other environments
9
-
10
- ## [1.0.1] - 2025-05-22
11
-
12
- ### Fixed
13
- - Ensure iso8601 timestamp format is available in other environments
14
-
15
- ## [1.0.0] - 2025-05-21
16
-
17
- ### Added
18
- - Initial release