sinlog 0.0.5 → 0.0.7

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.
data/docs/Readme.md CHANGED
@@ -22,18 +22,22 @@ A very, very simple Ruby singleton logger with colored log levels.
22
22
  Table of Contents (click to expand)
23
23
  </summary>
24
24
 
25
+ - [API DOC](#api-doc)
25
26
  - [Quick Start](#quick-start)
26
27
  - [Installation](#installation)
27
- - [Comparison](#comparison)
28
+ - [API Style](#api-style)
29
+ - [Procedural Style](#procedural-style)
30
+ - [OOP style](#oop-style)
31
+ - [FP Style](#fp-style)
32
+ - [Monkey Patching](#monkey-patching)
33
+ - [Comparison Table](#comparison-table)
28
34
  - [Method List](#method-list)
29
- - [Loggable \& LogExt](#loggable--logext)
30
- - [LogShortExt](#logshortext)
35
+ - [Mixin \& Refin](#mixin--refin)
36
+ - [ShortMixin \& ShortRefin](#shortmixin--shortrefin)
31
37
  - [Examples](#examples)
32
- - [Classic Method Call (Neither Mixin nor Refinement)](#classic-method-call-neither-mixin-nor-refinement)
33
- - [Mixin](#mixin)
34
38
  - [Refinement](#refinement)
39
+ - [Mixin](#mixin)
35
40
  - [Learn Sinlog API by Example](#learn-sinlog-api-by-example)
36
- - [Classic Method Call](#classic-method-call)
37
41
  - [Advanced](#advanced)
38
42
  - [Real World Example](#real-world-example)
39
43
  - [Log Levels](#log-levels)
@@ -43,11 +47,17 @@ Table of Contents (click to expand)
43
47
  - [Notes](#notes)
44
48
  - [Side Note](#side-note)
45
49
  - [Changelog](#changelog)
46
- - [0.0.3](#003)
50
+ - [v0.0.7 (2025-12-03)](#v007-2025-12-03)
47
51
  - [License](#license)
48
52
 
49
53
  </details>
50
54
 
55
+ ## API DOC
56
+
57
+ ![ClassDiagram](../misc/assets/svg/ClassDiagram.svg)
58
+
59
+ - Github Pages: <https://2moe.github.io/sinlog-gem>
60
+
51
61
  ## Quick Start
52
62
 
53
63
  ## Installation
@@ -58,31 +68,123 @@ Table of Contents (click to expand)
58
68
  gem install sinlog
59
69
  ```
60
70
 
61
- ### Comparison
71
+ ### API Style
72
+
73
+ In this library, a set of similar functionalities can be accessed through multiple different calling approaches.
74
+
75
+ Which style you choose mainly depends on your personal preference.
76
+
77
+
78
+ #### Procedural Style
79
+
80
+ ```ruby
81
+ require 'sinlog'
82
+
83
+ # update the Sinlog logger level
84
+ Sinlog.logger(level: "debug")
85
+ # OR: Sinlog::Logger.logger("debug")
86
+
87
+ Sinlog.dbg 'debug'
88
+ Sinlog.info 'information'
89
+ Sinlog.warn 'warning'
90
+ Sinlog.err 'error'
91
+ Sinlog.fatal 'fatal'
92
+ Sinlog.unk 'unknown'
93
+ ```
94
+
95
+ OR:
96
+
97
+ ```sh
98
+ # POSIX-sh
99
+
100
+ # Set an environment variable for a custom log level
101
+ export YOUR_CUSTOM_LOG=debug
102
+ ```
103
+
104
+ ```ruby
105
+ # RUBY
106
+
107
+ require 'sinlog'
108
+
109
+ log = Sinlog.logger(env_name: "YOUR_CUSTOM_LOG")
110
+ log.debug "This is a debug message"
111
+ log.info 'information'
112
+ log.warn 'warning'
113
+ log.error 'error'
114
+ log.fatal 'fatal'
115
+ log.unknown 'unknown'
116
+ ```
117
+
118
+ #### OOP style
119
+
120
+ ```ruby
121
+ require 'sinlog'
122
+
123
+ using Sinlog::Refin
124
+ # OR: include Sinlog::Mixin
125
+
126
+ 'debug'.log_dbg
127
+ 'information'.log_info
128
+ 'warning'.log_warn
129
+ 'error'.log_err
130
+ 'fatal'.log_fatal
131
+ 'unknown'.log_unk
132
+ ```
133
+
134
+ #### FP Style
135
+
136
+ ```ruby
137
+ require 'sinlog'
138
+
139
+ # update the Sinlog logger level
140
+ {level: "dbg"}.then { Sinlog.logger **_1 }
141
+
142
+ Log = Sinlog::Proc
62
143
 
63
- | Module | Type | Methods |
64
- | ----------- | ---------- | -------------------------------------------- |
65
- | Loggable | Mixin | `log_dbg`, `log_info`, etc. |
66
- | LogExt | Refinement | `log_dbg`, `log_info`, etc. |
67
- | LogShortExt | Refinement | `dbg`, `info`, `warn`, `err`, `fatal`, `unk` |
144
+ 'debug'.tap &Log.dbg
145
+ # OR: Log.dbg['debug']
146
+ # OR: Log.dbg.call 'debug'
147
+ # OR: Log.dbg.('debug')
148
+
149
+ class Object; def ▷(f) = f.call(self) end
150
+
151
+ true.▷(Log.dbg >> Log.info >> Log.warn >> Log.err >> Log.fatal >> Log.unk)
152
+ ```
153
+
154
+ ## Monkey Patching
155
+
156
+ ### Comparison Table
157
+
158
+ | Module | Type | Activation | Method Naming |
159
+ | ---------- | ---------- | ---------- | -------------------------------------------------------- |
160
+ | Mixin | Mixin | include | log_dbg, log_info, log_warn, log_err, log_fatal, log_unk |
161
+ | Refin | Refinement | using | log_dbg, log_info, log_warn, log_err, log_fatal, log_unk |
162
+ | ShortMixin | Mixin | include | dbg, info, warn, err, fatal, unk |
163
+ | ShortRefin | Refinement | using | dbg, info, warn, err, fatal, unk |
68
164
 
69
165
  ### Method List
70
166
 
71
- #### Loggable & LogExt
167
+ #### Mixin & Refin
72
168
 
73
- * `log_dbg` – DEBUG
74
- * `log_info` – INFO
75
- * `log_warn` – WARN
76
- * `log_err` – ERROR
77
- * `log_fatal` – FATAL
78
- * `log_unk` – UNKNOWN
169
+ - `log_dbg` – DEBUG
170
+ - `log_info` – INFO
171
+ - `log_warn` – WARN
172
+ - `log_err` – ERROR
173
+ - `log_fatal` – FATAL
174
+ - `log_unk` – UNKNOWN
79
175
 
80
- #### LogShortExt
176
+ #### ShortMixin & ShortRefin
81
177
 
82
- `LogShortExt` works the same way as `LogExt`, except for method naming:
83
178
 
84
- - `LogExt` methods use the `log_` prefix.
85
- - `LogShortExt` methods do not.
179
+ - **ShortRefin** is similar to **Refin**
180
+ - Apart from the difference in method naming, their internal implementations are identical.
181
+ - Methods in **Refin** have the `log_` prefix
182
+ - **ShortRefin** does not
183
+
184
+ - **ShortMixin** is similar to **Mixin**
185
+ - The only difference is in naming
186
+ - Methods in **Mixin** have the `log_` prefix
187
+ - **ShortMixin** does not
86
188
 
87
189
  ---
88
190
 
@@ -93,51 +195,55 @@ gem install sinlog
93
195
  - `fatal` – FATAL
94
196
  - `unk` – UNKNOWN
95
197
 
96
- > ⚠️ Note: `LogShortExt` defines a `warn` method, which overrides Ruby’s built-in `warn`.
97
- > If you need to call the original `warn "msg"`, use `Kernel.warn "msg"` instead.
198
+ > ⚠️ Since **ShortMixin** and **ShortRefin** define a `warn` method, they will override the default `warn`.
199
+ > For Ruby code that uses `warn "msg"` (which outputs to **stderr** rather than using a log format), you may need to manually change it to `Kernel.warn "msg"`.
98
200
  >
99
- > If this is a concern, use `using Sinlog::LogExt` instead of `using Sinlog::LogShortExt`.
201
+ > If this bothers you, then use `using Sinlog::Refin` instead of `using Sinlog::ShortRefin`.
100
202
 
101
203
  ### Examples
102
204
 
103
- #### Classic Method Call (Neither Mixin nor Refinement)
205
+ #### Refinement
104
206
 
105
207
  ```ruby
106
208
  require 'sinlog'
209
+ using Sinlog::Refin
210
+ { dir: "/path/to/xx" }.log_info
211
+ ```
107
212
 
108
- log = Sinlog.logger
109
- log.info "Information"
110
- log.debug "This is a debug message"
213
+ ```ruby
214
+ require 'sinlog'
215
+ using Sinlog::ShortRefin
216
+ { dir: "/path/to/xx" }.info
111
217
  ```
112
218
 
113
219
  #### Mixin
114
220
 
115
221
  ```ruby
116
222
  require 'sinlog'
117
- include Sinlog::Loggable
223
+ include Sinlog::Mixin
118
224
  "Hello".log_info
119
225
  ```
120
226
 
121
- #### Refinement
122
-
123
227
  ```ruby
124
228
  require 'sinlog'
125
- using Sinlog::LogExt
126
- { dir: "/path/to/xx" }.log_info
229
+ include Sinlog::ShortMixin
230
+ "Hello".info
127
231
  ```
128
232
 
129
233
  ## Learn Sinlog API by Example
130
234
 
131
- <img src="../assets/img/preview.png" alt="preview">
235
+ <img src="../misc/assets/img/preview.webp" alt="preview">
132
236
 
133
237
  ```ruby
134
238
  require 'sinlog'
135
239
 
136
- class A
137
- using Sinlog::LogShortExt
240
+ module A
241
+ module_function
242
+ using Sinlog::ShortRefin
138
243
 
139
- def self.log
140
- 'Hello, this is a debug message.'.dbg
244
+ def log
245
+ ['Hey hey hey, could you see this debug message?',
246
+ 'You might find it a bit verbose, hahaha!'].dbg
141
247
  'Just some info.'.info
142
248
 
143
249
  'FBI, open the door!'.warn
@@ -147,42 +253,14 @@ class A
147
253
  end
148
254
  end
149
255
 
150
- Sinlog::LV[:info].then do
151
- Sinlog.logger_with_level it
152
- end
153
-
154
256
  A.log
155
- ```
156
-
157
- ### Classic Method Call
158
257
 
159
- If you prefer the traditional style (`log.info(msg)` instead of `msg.info`):
160
-
161
- ```ruby
162
- require 'sinlog'
163
-
164
- log = Sinlog.logger
165
-
166
- log.debug 'debug'
167
- log.info 'information'
168
- log.warn 'warning'
169
- log.error 'error'
170
- log.fatal 'fatal'
171
- log.unknown 'unknown'
258
+ # update the log level to error
259
+ Sinlog.logger(level: 'err')
260
+ Kernel.warn 'Logger.level => error'
261
+ A.log
172
262
  ```
173
263
 
174
- > The data type of `Sinlog.logger` is Ruby’s standard library `Logger`.
175
-
176
- In addition to the common methods listed above, you can also use other methods such as `.reopen`.
177
- For details, see <https://docs.ruby-lang.org/en/3.4/Logger.html>
178
-
179
- - `debug`
180
- - `info`
181
- - `warn`
182
- - `error`
183
- - `fatal`
184
- - `unknown`
185
-
186
264
  ## Advanced
187
265
 
188
266
  After trying it out ourselves, we have a basic understanding of `sinlog`.
@@ -200,7 +278,8 @@ require 'sinlog'
200
278
  class EpubProcessor
201
279
  def initialize(epub_file, logger = nil)
202
280
  @epub = epub_file
203
- @logger = logger || Sinlog.instance.tap { it.set_level_from_env!("XX_LOG") }.logger
281
+ logger ||= Sinlog::logger(env_name: "XX_LOG")
282
+ @logger = logger
204
283
  @logger.debug "EpubProcessor class initialization completed."
205
284
  end
206
285
  end
@@ -223,16 +302,18 @@ Log levels from low to high are:
223
302
  - fatal = 4
224
303
  - unknown = 5
225
304
 
305
+ > Interestingly, the log levels in Ruby’s standard library `Logger` are the opposite of Rust’s [log::Level](https://docs.rs/log/latest/log/enum.Level.html).
306
+
226
307
  ```ruby
227
308
  p Sinlog::LV
228
309
  # => {debug: 0, info: 1, warn: 2, error: 3, fatal: 4, unknown: 5}
229
310
 
230
311
  # Change the log level to warn
231
- log = Sinlog.logger_with_level(Sinlog::LV[:warn])
312
+ log = Sinlog.logger(level: 'warn')
232
313
  # OR:
233
- # log = Sinlog.logger.tap { it.level = Sinlog::LV[:warn] }
314
+ # log = Sinlog.logger(level: Sinlog::LV[:warn])
234
315
  # OR:
235
- # log = Sinlog.instance.logger.tap { it.level = 2 }
316
+ # log = Sinlog.logger.tap { it.level = 2 }
236
317
 
237
318
  log.error "This message will be displayed! Lower level WARN (2) will display higher level ERROR (3) logs."
238
319
  log.info "This message will not be displayed! Higher level WARN (2) will not display lower level INFO (1) logs."
@@ -249,13 +330,11 @@ To allow them to configure `log.level` directly, we can use environment variable
249
330
 
250
331
  > Using environment variables is simple and efficient.
251
332
 
252
- By default, Sinlog will attempt to read the value of the environment variable `RUBY_LOG`.
253
-
254
- It essentially calls the function `set_level_from_env!(env_name = 'RUBY_LOG')`.
333
+ By default, `Sinlog::Logger` will attempt to read the value of the environment variable `RUBY_LOG`.
255
334
 
256
335
  - If the environment variable does not exist, it uses `debug(0)`.
257
- - If the environment variable exists but is empty, it uses `unknown(5)`.
258
- - If the environment variable's value is invalid, it uses `unknown(5)`.
336
+ - If the environment variable exists but is empty, it uses `error(3)`.
337
+ - If the environment variable's value is invalid, it uses `error(3)`.
259
338
 
260
339
  We can set the environment variable using POSIX-sh, and then the logger will automatically set the log level to `warn` (the value of `RUBY_LOG`) during initialization.
261
340
 
@@ -275,7 +354,7 @@ export XX_CLI_LOG=info
275
354
  Ruby:
276
355
 
277
356
  ```ruby
278
- logger = Sinlog.instance.tap { it.set_level_from_env!("XX_CLI_LOG") }.logger
357
+ logger = Sinlog.logger(env_name:"XX_CLI_LOG")
279
358
 
280
359
  logger.debug "This message will not be displayed because the current log level is INFO(1)."
281
360
  logger.info "Hello!"
@@ -285,7 +364,9 @@ logger.info "Hello!"
285
364
 
286
365
  By default, Sinlog outputs to `STDERR`.
287
366
 
288
- If you need to customize the log output path, you can call the Logger's `reopen` method.
367
+ > The data type of `Sinlog.logger` is `::Logger` (from Ruby's standard library).
368
+
369
+ If you need to customize the log output path, you can call the `::Logger`'s `reopen` method.
289
370
 
290
371
  ```ruby
291
372
  # Logs will be output to the file a.log
@@ -305,13 +386,15 @@ log.error "What happened! QuQ"
305
386
 
306
387
  ### Other Logger Methods
307
388
 
308
- In addition to `.reopen` and `.level`, we can also call other methods from Ruby's standard library logger on `Sinlog.instance.logger`.
389
+ In addition to `.reopen` and `.level`, we can also call other methods from Ruby's standard library logger on `Sinlog.logger`.
390
+
391
+ For details, see <https://docs.ruby-lang.org/en/3.4/Logger.html>
309
392
 
310
393
  ### Notes
311
394
 
312
- Sinlog uses the Singleton pattern, meaning the entire program will share the same instance (logger).
395
+ `Sinlog::Logger` uses the Singleton pattern, meaning the entire program will share the same instance (logger).
313
396
 
314
- Modifying Sinlog in class A of the same program will affect Sinlog in class B.
397
+ Modifying `Sinlog.logger` (a.k.a. `Sinlog::Logger.instance.logger`) in class A of the same program will affect `Sinlog::Logger` in class B.
315
398
 
316
399
  ## Side Note
317
400
 
@@ -320,21 +403,32 @@ The API might not fully adhere to idiomatic Ruby usage, so I appreciate your und
320
403
 
321
404
  ## Changelog
322
405
 
323
- ### 0.0.3
324
-
325
- - `Sinlog.instance.logger` can be simplified => `Sinlog.logger`
406
+ [Earlier versions](./Changelog.md)
326
407
 
327
- - add `Sinlog.logger_with_level`
328
- - e.g., `logger = Sinlog.logger_with_level(Sinlog::LV[:warn])`
329
- - old: `Sinlog.instance.logger.tap { it.level = Sinlog::LV[:warn] }`
408
+ ### v0.0.7 (2025-12-03)
330
409
 
331
- - add `LogExt`, `LogShortExt` and `Loggable`
332
-
333
- - add sorbet **.rbi** files
410
+ - add `Sinlog::Proc` module
411
+ - add `lib/sinlog/08_module_short_ext.rb`:
412
+ - `Sinlog.dbg`
413
+ - `Sinlog.info`
414
+ - `Sinlog.warn`
415
+ - `Sinlog.err`
416
+ - `Sinlog.fatal`
417
+ - `Sinlog.unk`
418
+ - update `Sinlog::Logger.logger`
419
+ - Previous: `def self.logger`
420
+ - Current: `def self.logger(level = nil, env_name = nil)`
334
421
 
335
422
  Breaking changes:
336
- - `fetch_env_and_update_log_level(ENV_NAME)` => `set_level_from_env!(ENV_NAME)`
337
- - remove `LogLambdaExt` and related modules
423
+
424
+ - `Sinlog.to_log_level` => `Sinlog.as_log_level`
425
+ - rename `lib/sinlog/*.rb`
426
+ - consts => 01_consts
427
+ - logger => 02_logger
428
+ - module_fn => 03_module_fn
429
+ - log_ext => 04_log_ext
430
+ - short_ext => 05_short_ext
431
+ - loggable => 06_loggable
338
432
 
339
433
  ## License
340
434
 
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ module Sinlog
6
+ StdLogger = ::Logger
7
+
8
+ # Define colors for different log levels
9
+ COLORS = {
10
+ debug: "\e[34m", # Blue
11
+ info: "\e[36m", # Cyan
12
+ warn: "\e[33m", # Yellow
13
+ error: "\e[31m", # Red
14
+ fatal: "\e[35m", # Magenta
15
+ unknown: "\e[0m", # Reset
16
+ }.freeze
17
+
18
+ # log levels
19
+ LV = {
20
+ debug: StdLogger::DEBUG,
21
+ info: StdLogger::INFO,
22
+ warn: StdLogger::WARN,
23
+ error: StdLogger::ERROR,
24
+ fatal: StdLogger::FATAL,
25
+ unknown: StdLogger::UNKNOWN,
26
+ }.freeze
27
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sinlog # rubocop:disable Style/ClassAndModuleChildren
4
+ # Logger Singleton Class
5
+ class Logger
6
+ require 'singleton'
7
+
8
+ include Singleton
9
+ attr_reader :logger
10
+
11
+ # Since this is a Singleton class, you should use {.instance} instead of `.new`.
12
+ #
13
+ # @return [self] Sinlog::Logger
14
+ #
15
+ # @example
16
+ #
17
+ # instance = Sinlog::Logger.instance
18
+ # instance.logger.info "Hello"
19
+ def initialize
20
+ @logger = StdLogger.new($stderr)
21
+ set_level_from_env!
22
+ @logger.formatter = Kernel.proc do |severity, datetime, progname, msg|
23
+ color = COLORS[severity.downcase.to_sym]
24
+ reset = COLORS[:unknown]
25
+ formatted_datetime = datetime.strftime('%H:%M:%S.%L')
26
+ prog = format_prog_name(progname)
27
+ "[#{color}#{severity}#{reset}] #{formatted_datetime} #{prog}#{msg}\n"
28
+ end
29
+ end
30
+
31
+ # Configures and returns the {StdLogger}.
32
+ #
33
+ # @note Similar to {Sinlog.logger}, but uses different parameter types.
34
+ #
35
+ # - {Sinlog.logger}: uses keyword arguments, e.g., (level: "info", env_name: "RUBY_LOG")
36
+ # - This function: uses positional arguments, e.g., ("warn", "CUSTOM_LOG")
37
+ #
38
+ # @param level [Integer, String, Symbol, nil] Log Level.
39
+ # @param env_name [#to_s] Name of the environment variable.
40
+ #
41
+ # @return [StdLogger]
42
+ #
43
+ # @see Sinlog.logger
44
+ #
45
+ # ## Example
46
+ #
47
+ # log = Sinlog::Logger.logger("debug")
48
+ # log.info "Information"
49
+ # log.debug "This is a debug message"
50
+ #
51
+ # The log output format will be similar to:
52
+ #
53
+ # <ul>
54
+ # <li><p><span style="color:darkcyan;">[INFO]</span> 21:29:22.004 Information</p></li>
55
+ # <li><p><span style="color:blue;">[DEBUG]</span> 21:29:22.005 This is a debug message</p></li>
56
+ # </ul>
57
+ #
58
+ # > Where "INFO" is highlighted in cyan and "DEBUG" is highlighted in blue.
59
+ #
60
+ # The default log level is set based on the `RUBY_LOG` environment variable.
61
+ #
62
+ # If this variable is not set, the default level is `DEBUG`.
63
+ def self.logger(level = nil, env_name = nil)
64
+ Sinlog.logger(level:, env_name:)
65
+ end
66
+
67
+ # Sets the `@logger.level` (**log level**) based on the value of an environment variable.
68
+ #
69
+ # If `env_name` is not specified, it reads the value of the `RUBY_LOG` environment variable.
70
+ #
71
+ # - If the value exists, it is converted to lowercase, then to a symbol, and looked up in the LV hash;
72
+ # - If it does not exist, the default level is `DEBUG(0)`;
73
+ # - If the lookup result is invalid, the level is set to `ERROR(3)`;
74
+ # - If the environment variable value is empty, the lookup result will be invalid,
75
+ # and the level will be set to `ERROR(3)`.
76
+ #
77
+ # @example
78
+ #
79
+ # ENV["XX_LOG"] = "info" # or setenv in posix-sh: export XX_LOG=info
80
+ #
81
+ # level = Sinlog.instance.set_level_from_env!("XX_LOG")
82
+ # level == Sinlog::LV[:info] # => true
83
+ #
84
+ # log = Sinlog.logger
85
+ # log.debug "This message will not be displayed because the current log level is info"
86
+ # log.info "Hello!"
87
+ #
88
+ # @return [Integer] `@logger.level`
89
+ # @param env_name [#to_s] Name of the environment variable.
90
+ def set_level_from_env!(env_name = 'RUBY_LOG')
91
+ env_str = ENV[env_name.to_s]&.downcase || 'debug'
92
+
93
+ Sinlog
94
+ .as_log_level(env_str)
95
+ .tap { @logger.level = _1 }
96
+ end
97
+
98
+ private
99
+
100
+ def format_prog_name(progname)
101
+ return '' if progname.to_s.empty?
102
+
103
+ green = "\e[32m"
104
+ reset = "\e[0m"
105
+ space = ' '
106
+ "<#{green}#{progname}#{reset}>#{space}"
107
+ end
108
+ end
109
+ end