logaru 1.0.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 +7 -0
- data/CHANGELOG.md +26 -0
- data/LICENSE +21 -0
- data/README.md +185 -0
- data/Rakefile +12 -0
- data/docs/ARCHITECTURE.md +107 -0
- data/docs/FEATURES.md +38 -0
- data/docs/SECURITY.md +38 -0
- data/lib/logaru/errors.rb +18 -0
- data/lib/logaru/formatter.rb +54 -0
- data/lib/logaru/level.rb +42 -0
- data/lib/logaru/logger.rb +170 -0
- data/lib/logaru/version.rb +5 -0
- data/lib/logaru.rb +17 -0
- metadata +118 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 3192f41c03547e6173ca0002334fc95e8ee10a5d377891a84b5b543f53dd9306
|
|
4
|
+
data.tar.gz: c05dfdb1f92db1cb6518c3850360cc0a4a8f049e4e32bdd9a18404f66459f9d2
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: f31e77c1b4b91420bc45aa4fffbacaa61995cc054a8e2fcac74fbdb06fe1a7fd2ee996302cab4e956d03bdd5f2123f5bfc75703e1784045c6c35db4c9d80ff37
|
|
7
|
+
data.tar.gz: 691e1faa39da9edb5971c8723fe1477be2749ef16853fe45c3d1491d68b51b74f7dc792ba4cba447465fea84130209c30107ea147b70d585dc84915f373a71ea
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [1.0.0] - 2026-09-24
|
|
11
|
+
|
|
12
|
+
First stable release. Requires Ruby >= 3.3 and has no runtime dependencies.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- `Logaru::Logger` with `debug`, `info`, `warn`, `error`, `fatal` and `unknown`, plus the generic `log(severity, message, progname:)`.
|
|
17
|
+
- Level filtering: a severity is accepted as a `Logaru::Level` constant, a name or a symbol, and entries below the configured level are discarded before formatting.
|
|
18
|
+
- `Logaru::Logger` writes to `$stdout` by default, to a log file (`String` or `Pathname`: parent directories created, append mode, handle reused, flushed on every write) or to any object responding to `#write`.
|
|
19
|
+
- `Logaru::Logger#formatter`, `#level`, `#output` and `#device` readers, plus `#close`, which releases the file opened by the logger so it can be rotated externally — the next write reopens it.
|
|
20
|
+
- `Logaru::Formatter` with a configurable pattern (`pattern:` or a block), pattern arity validation and `Logaru::Formatter::DEFAULT_PATTERN`.
|
|
21
|
+
- `Logaru::Level` severities, `coerce` and `name_for`.
|
|
22
|
+
- Error hierarchy under `Logaru::Error`: `InvalidFormatterError`, `InvalidLevelError`, `InvalidPatternError` and `InvalidOutputError`, all raised from `Logger.new`/`Formatter.new`.
|
|
23
|
+
- Thread safety: writes, device resolution and `#close` are serialized by a mutex and the log file is opened only once, so one logger can be shared between threads.
|
|
24
|
+
|
|
25
|
+
[Unreleased]: https://github.com/rpzerosixcode/logaru/compare/v1.0.0...HEAD
|
|
26
|
+
[1.0.0]: https://github.com/rpzerosixcode/logaru/releases/tag/v1.0.0
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 rpzerosixcode
|
|
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 all
|
|
13
|
+
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 THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# Logaru
|
|
2
|
+
|
|
3
|
+
[](https://github.com/rpzerosixcode/logaru/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.ruby-lang.org)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
A Ruby library focused on clear, organized, and configurable log output.
|
|
8
|
+
|
|
9
|
+
> 🇧🇷 **Made in Brazil.**
|
|
10
|
+
>
|
|
11
|
+
> Logaru is a Brazilian Ruby gem, developed in Brazil.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
Add Logaru to your Gemfile:
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
gem "logaru"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Then run:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
bundle install
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Or install it directly:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
gem install logaru
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
require "logaru"
|
|
37
|
+
|
|
38
|
+
Logaru::VERSION # => "1.0.0"
|
|
39
|
+
|
|
40
|
+
Logaru.root # => absolute path to the gem root
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The logger supports `debug`, `info`, `warn`, `error`, `fatal`, and `unknown` messages. Levels can be configured with a `Logaru::Level` constant, a level name, or a symbol:
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
logger = Logaru::Logger.new(level: :info)
|
|
47
|
+
|
|
48
|
+
logger.debug("This message is ignored")
|
|
49
|
+
|
|
50
|
+
logger.info("Application started", progname: "web")
|
|
51
|
+
|
|
52
|
+
logger.error("An unexpected error occurred")
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Pass `file:` to append messages to a file. Log files are opened on the first write, reused afterwards, and their parent directories are created automatically:
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
logger = Logaru::Logger.new(file: "log/application.log")
|
|
59
|
+
|
|
60
|
+
logger.info("Application started")
|
|
61
|
+
|
|
62
|
+
logger.output # => "log/application.log"
|
|
63
|
+
|
|
64
|
+
logger.device # => the File object used for writing
|
|
65
|
+
|
|
66
|
+
logger.close # closes the file; it is reopened on the next write
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
A `Pathname` or any object responding to `#write` (such as `StringIO` or an already open `File`) is accepted as well. Writes are flushed by default (`sync: true`) so the file is always up to date, and the logger only closes files it opened itself — streams provided through `file:` remain the caller's responsibility.
|
|
70
|
+
|
|
71
|
+
Rotating the log is left to your tooling: call `logger.close`, rename or replace the file, and the next entry reopens the path (see [Architecture](docs/ARCHITECTURE.md#log-file-lifecycle)).
|
|
72
|
+
|
|
73
|
+
### Formatters
|
|
74
|
+
|
|
75
|
+
Every logger builds its own `Logaru::Formatter`, so customizing the output never requires injecting one:
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
logger = Logaru::Logger.new(level: :debug, file: "log/application.log") do |severity, datetime, progname, message|
|
|
79
|
+
"[#{datetime}] #{progname || "app"} #{Logaru::Level.name_for(severity).upcase}: #{message}\n"
|
|
80
|
+
end
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The same pattern can be passed as an option, and a formatter instance can still be injected to share one configuration between loggers (`formatter:` and `pattern:` are mutually exclusive):
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
pattern = proc { |severity, datetime, progname, message| "#{severity} #{message}\n" }
|
|
87
|
+
|
|
88
|
+
Logaru::Logger.new(pattern: pattern)
|
|
89
|
+
|
|
90
|
+
formatter = Logaru::Formatter.new(pattern: pattern)
|
|
91
|
+
|
|
92
|
+
Logaru::Logger.new(formatter: formatter)
|
|
93
|
+
|
|
94
|
+
Logaru::Logger.new(formatter: formatter, pattern: pattern)
|
|
95
|
+
|
|
96
|
+
# => Logaru::InvalidFormatterError: pass either formatter or pattern, not both
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Formatters keep no global state, so each logger can use its own pattern. A pattern must be able to receive the four arguments (`severity`, `datetime`, `progname`, `message`); variadic patterns such as `|*arguments|` are accepted too:
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
Logaru::Formatter.new { |message| "#{message}\n" }
|
|
103
|
+
|
|
104
|
+
# => Logaru::InvalidPatternError: pattern must accept 4 arguments
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Thread safety
|
|
108
|
+
|
|
109
|
+
A logger can be shared between threads: writes are serialized, so entries are never interleaved and the log file is opened only once, even when several threads start together. Patterns run outside that lock, so they should not depend on mutable shared state — see [Concurrency](docs/ARCHITECTURE.md#concurrency).
|
|
110
|
+
|
|
111
|
+
### Errors
|
|
112
|
+
|
|
113
|
+
Every error raised by Logaru inherits from `Logaru::Error`:
|
|
114
|
+
|
|
115
|
+
* `Logaru::InvalidFormatterError` — the formatter does not respond to `#format`.
|
|
116
|
+
* `Logaru::InvalidLevelError` — the configured severity is not supported.
|
|
117
|
+
* `Logaru::InvalidPatternError` — the formatter pattern is not callable or cannot receive the log arguments.
|
|
118
|
+
* `Logaru::InvalidOutputError` — the logger output is neither a path nor an object responding to `#write`.
|
|
119
|
+
|
|
120
|
+
## Development
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
git clone https://github.com/rpzerosixcode/logaru.git
|
|
124
|
+
|
|
125
|
+
cd logaru
|
|
126
|
+
|
|
127
|
+
bundle install
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Run the test suite:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
bundle exec rspec
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Run the linter (must be clean — CI fails on offenses):
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
bundle exec rubocop
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Run both (default Rake task):
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
bundle exec rake
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Sanity-check the gem build (also run in CI):
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
gem build logaru.gemspec --strict
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Releasing
|
|
155
|
+
|
|
156
|
+
Release by pushing a tag that matches `Logaru::VERSION` (see `lib/logaru/version.rb`):
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
git tag v0.1.0
|
|
160
|
+
|
|
161
|
+
git push origin v0.1.0
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
`.github/workflows/release.yml` then runs the same checks as CI (RSpec, RuboCop and a strict gem build on Ruby 3.3, 3.4 and 4.0), checks the tag against `Logaru::VERSION`, publishes the gem to RubyGems and opens a GitHub release with the gem attached. Publishing uses RubyGems trusted publishing (OIDC), so no API token is stored in the repository — configure a trusted publisher for `logaru` on RubyGems.org (workflow file `.github/workflows/release.yml`) before the first tag.
|
|
165
|
+
|
|
166
|
+
## Documentation
|
|
167
|
+
|
|
168
|
+
* [Architecture](docs/ARCHITECTURE.md) — components, entry lifecycle, output resolution and concurrency model.
|
|
169
|
+
* [Features](docs/FEATURES.md) — what the library does today and what is still missing.
|
|
170
|
+
* [Security](docs/SECURITY.md) — threat surface, known limitations and how to report a vulnerability.
|
|
171
|
+
|
|
172
|
+
The repository layout, the CI matrix and the Dependabot setup are documented in [Architecture](docs/ARCHITECTURE.md#repository-layout).
|
|
173
|
+
|
|
174
|
+
## Requirements
|
|
175
|
+
|
|
176
|
+
* Ruby >= 3.3.0 (see `logaru.gemspec`).
|
|
177
|
+
|
|
178
|
+
## License
|
|
179
|
+
|
|
180
|
+
Logaru is available under the MIT License — see [LICENSE](LICENSE).
|
|
181
|
+
|
|
182
|
+
## Links
|
|
183
|
+
|
|
184
|
+
* Homepage: https://github.com/rpzerosixcode/logaru
|
|
185
|
+
* Changelog: https://github.com/rpzerosixcode/logaru/blob/main/CHANGELOG.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
|
+
require "rubocop/rake_task"
|
|
6
|
+
|
|
7
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
8
|
+
|
|
9
|
+
RuboCop::RakeTask.new(:rubocop)
|
|
10
|
+
|
|
11
|
+
desc "Run spec and rubocop"
|
|
12
|
+
task default: %i[spec rubocop]
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
How Logaru is built internally. Installation and usage live in the [README](../README.md); the capability checklist is in [FEATURES.md](FEATURES.md).
|
|
4
|
+
|
|
5
|
+
## Design goals
|
|
6
|
+
|
|
7
|
+
* **Formatting without global state:** each logger owns its formatter, so changing the output of one logger never changes another.
|
|
8
|
+
* **Fail fast:** level, formatter, pattern and output are validated in `Logger.new` instead of failing on the first write.
|
|
9
|
+
* **Cheap writes:** entries below the configured level are discarded before formatting, and the log file is opened once and reused.
|
|
10
|
+
* **Safe sharing:** one logger can be used by several threads without interleaved or lost entries.
|
|
11
|
+
* **No runtime dependencies:** standard library only (`fileutils`, `pathname`).
|
|
12
|
+
|
|
13
|
+
## Components
|
|
14
|
+
|
|
15
|
+
| Component | File | Responsibility |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| `Logaru` | `lib/logaru.rb` | Loads the components and exposes `Logaru.root`. |
|
|
18
|
+
| `Logaru::VERSION` | `lib/logaru/version.rb` | Gem version. |
|
|
19
|
+
| `Logaru::Level` | `lib/logaru/level.rb` | Numeric severities (`DEBUG`…`UNKNOWN`), `coerce` (constant, name or symbol) and `name_for`. |
|
|
20
|
+
| `Logaru::Formatter` | `lib/logaru/formatter.rb` | Owns the pattern, validates its arity and turns an entry into text (`#format`). |
|
|
21
|
+
| `Logaru::Logger` | `lib/logaru/logger.rb` | Filters by level, resolves the output, manages the log file and serializes writes with a mutex. |
|
|
22
|
+
| `Logaru::Error` | `lib/logaru/errors.rb` | Error hierarchy: `InvalidFormatterError`, `InvalidLevelError`, `InvalidPatternError`, `InvalidOutputError`. |
|
|
23
|
+
|
|
24
|
+
## Entry lifecycle
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
logger.info("message", progname: "web")
|
|
28
|
+
-> severity = Level.coerce(INFO)
|
|
29
|
+
-> return if severity < @level # no formatting, no write, no file created
|
|
30
|
+
-> @formatter.format(severity, Time.now, progname, message) # outside the lock
|
|
31
|
+
-> @mutex.synchronize { resolved_device.write(text) }
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Output resolution
|
|
35
|
+
|
|
36
|
+
| `file:` value | Device used | Owner |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| omitted or `nil` | `$stdout`, resolved on every write | process |
|
|
39
|
+
| `String`, `Pathname`, or an object with `to_path` and without `write` | file opened in append mode | the logger (`#close` releases it) |
|
|
40
|
+
| object responding to `#write` (`File`, `StringIO`, `Tempfile`, custom device) | the object itself | the caller, never closed by Logaru |
|
|
41
|
+
|
|
42
|
+
`Pathname` is classified explicitly: it responds to `#write`, but that call replaces the whole file, so treating it as a stream would overwrite the log on every entry. Path-like values are therefore detected before the `#write` check, and `#output` always returns the value given to `file:`.
|
|
43
|
+
|
|
44
|
+
## Log file lifecycle
|
|
45
|
+
|
|
46
|
+
1. Opened on the first write that passes the level, so a logger that logs nothing creates neither the file nor its directories.
|
|
47
|
+
2. Parent directories are created with `FileUtils.mkdir_p`.
|
|
48
|
+
3. Opened in append mode with `sync` (default `true`), so every entry is flushed on write.
|
|
49
|
+
4. The handle is reused between writes and exposed by `#device`.
|
|
50
|
+
5. `#close` releases only the file the logger opened, and the next write reopens the path in append mode — the supported path for external rotation (close, rename, keep logging).
|
|
51
|
+
|
|
52
|
+
## Concurrency
|
|
53
|
+
|
|
54
|
+
* A single `Mutex` per logger serializes device resolution, writing and `#close`, so entries never interleave, none is lost and the file is opened exactly once even when threads start together.
|
|
55
|
+
* Formatting runs outside the lock (it is the expensive step), so patterns must not depend on mutable shared state — keep them pure.
|
|
56
|
+
* Locking is per logger and per process: Logaru does not use `flock`, so independent processes writing to the same file are not synchronized.
|
|
57
|
+
|
|
58
|
+
## Repository layout
|
|
59
|
+
|
|
60
|
+
```text
|
|
61
|
+
lib/
|
|
62
|
+
logaru.rb # entry point — Logaru namespace
|
|
63
|
+
logaru/version.rb # Logaru::VERSION
|
|
64
|
+
logaru/errors.rb # Logaru::Error and the specific errors
|
|
65
|
+
logaru/level.rb # Logaru::Level severities
|
|
66
|
+
logaru/formatter.rb # Logaru::Formatter — patterns and arity validation
|
|
67
|
+
logaru/logger.rb # Logaru::Logger — levels, output and file management
|
|
68
|
+
|
|
69
|
+
spec/
|
|
70
|
+
spec_helper.rb # RSpec configuration (loads spec/support)
|
|
71
|
+
unit/ # Unit tests
|
|
72
|
+
integration/ # Integration tests
|
|
73
|
+
e2e/ # End-to-end tests (placeholder)
|
|
74
|
+
support/ # Helpers / shared examples
|
|
75
|
+
|
|
76
|
+
docs/ # User documentation (README + this file, FEATURES, SECURITY)
|
|
77
|
+
|
|
78
|
+
.github/workflows/ # ci.yml — RSpec + RuboCop + gem build
|
|
79
|
+
.github/dependabot.yml # weekly dependency updates
|
|
80
|
+
|
|
81
|
+
Rakefile # default task: spec + rubocop
|
|
82
|
+
logaru.gemspec # Gem metadata and dependencies
|
|
83
|
+
Gemfile # source + gemspec
|
|
84
|
+
.rubocop.yml # Style rules
|
|
85
|
+
.rspec # --require spec_helper
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Automated checks
|
|
89
|
+
|
|
90
|
+
CI (`.github/workflows/ci.yml`) runs on every push and pull request to `main` and `develop`:
|
|
91
|
+
|
|
92
|
+
* RSpec suite and `bundle exec rubocop --parallel` on `ubuntu-latest` with Ruby `3.3`, `3.4` and `4.0` (fail-fast disabled);
|
|
93
|
+
* `gem build logaru.gemspec --strict` as a packaging sanity check;
|
|
94
|
+
* Dependabot opens weekly pull requests for Bundler and GitHub Actions dependencies.
|
|
95
|
+
|
|
96
|
+
Locally, `bundle exec rake` runs the same checks available in the development environment.
|
|
97
|
+
|
|
98
|
+
## Release pipeline
|
|
99
|
+
|
|
100
|
+
`.github/workflows/release.yml` runs when a `v*` tag is pushed (the tagging steps are in the [README](../README.md#releasing)):
|
|
101
|
+
|
|
102
|
+
1. **Validate** — the same matrix and checks as CI: RSpec, `bundle exec rubocop --parallel` and `gem build logaru.gemspec --strict` on `ubuntu-latest` with Ruby `3.3`, `3.4` and `4.0` (fail-fast disabled).
|
|
103
|
+
2. **Publish** — fails unless the tag equals `Logaru::VERSION`, rebuilds the gem, authenticates to RubyGems over OIDC (trusted publishing), pushes the gem and creates the GitHub release with the gem attached.
|
|
104
|
+
|
|
105
|
+
The validate job is a mirror of the CI test job, so a tag can never publish code that CI would reject. Publishing runs `gem build logaru.gemspec --strict`, which writes `logaru-<version>.gem` in the workspace; locally, `bundle exec rake build` (from `bundler/gem_tasks`, required by the `Rakefile`) writes `pkg/logaru-<version>.gem`. `pkg/` and `*.gem` are git-ignored. The release job declares `id-token: write` for OIDC and `contents: write` to create the release.
|
|
106
|
+
|
|
107
|
+
`Logaru::VERSION` (`lib/logaru/version.rb`) is the single source of truth: the gemspec reads it, the workflow compares it with the tag and `Gemfile.lock` records it. A release therefore means bumping that file, moving the `Unreleased` entries into a dated section in `CHANGELOG.md` and pushing the `v<version>` tag.
|
data/docs/FEATURES.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Features
|
|
2
|
+
|
|
3
|
+
What Logaru offers in `1.0.0`. Usage examples are in the [README](../README.md); internal design in [ARCHITECTURE.md](ARCHITECTURE.md).
|
|
4
|
+
|
|
5
|
+
## Severities and filtering
|
|
6
|
+
|
|
7
|
+
* Six severities: `debug`, `info`, `warn`, `error`, `fatal` and `unknown`, plus the generic `log(severity, message, progname:)`.
|
|
8
|
+
* A level can be given as a `Logaru::Level` constant, a name or a symbol, in any case.
|
|
9
|
+
* Entries below the configured level are discarded before formatting: they cost nothing and never create a log file. The default level is `debug`.
|
|
10
|
+
|
|
11
|
+
## Output
|
|
12
|
+
|
|
13
|
+
* Standard output by default (`$stdout`, resolved on every write).
|
|
14
|
+
* Log files through a `String` or a `Pathname`: created on first use, parent directories created automatically, append mode, handle reused, one flush per entry.
|
|
15
|
+
* Any object responding to `#write` (`File`, `StringIO`, `Tempfile`, custom devices) — streams Logaru did not open are never closed by it.
|
|
16
|
+
* `sync: false` for buffered writes, `#close` to release the file, `#device` and `#output` for introspection.
|
|
17
|
+
|
|
18
|
+
## Formatting
|
|
19
|
+
|
|
20
|
+
* Default pattern (`[<timestamp>] [<progname> ]<LEVEL>: <message>`) or a custom `pattern:`/block for each logger.
|
|
21
|
+
* A `Logaru::Formatter` instance can be built once and injected into several loggers.
|
|
22
|
+
* Patterns are validated when created: they must be callable and able to receive the four entry arguments (severity, datetime, progname, message). Lambdas, variadic blocks, optional parameters and callables without `#parameters` are supported.
|
|
23
|
+
* Optional `progname` per entry.
|
|
24
|
+
|
|
25
|
+
## Concurrency
|
|
26
|
+
|
|
27
|
+
* A logger can be shared between threads: entries are written atomically and the log file is opened exactly once.
|
|
28
|
+
* `#close` is safe while other threads are logging.
|
|
29
|
+
|
|
30
|
+
## Operations
|
|
31
|
+
|
|
32
|
+
* External rotation (rename or replace the file) works through `#close` and the automatic reopen on the next write.
|
|
33
|
+
* Invalid configuration (formatter, level, pattern, output) raises a `Logaru::Error` subclass at `Logger.new`.
|
|
34
|
+
* No runtime dependencies; Ruby >= 3.3.
|
|
35
|
+
|
|
36
|
+
## Not available yet
|
|
37
|
+
|
|
38
|
+
Log rotation by size or date, asynchronous logging, ANSI colors, message sanitization (see [SECURITY.md](SECURITY.md)) and cross-process locking are not implemented. They are candidates for future minor versions and do not affect the `1.0` API, which follows Semantic Versioning.
|
data/docs/SECURITY.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
How Logaru handles untrusted input, and what it does not protect against.
|
|
4
|
+
|
|
5
|
+
Supported versions: the latest `1.x` release. `1.0.0` is the first stable version; fixes are prepared on `develop`, merged into `main` and published as a new patch or minor release from a tag matching `Logaru::VERSION`.
|
|
6
|
+
|
|
7
|
+
## Threat surface
|
|
8
|
+
|
|
9
|
+
* No runtime dependencies — only the Ruby standard library (`fileutils`, `pathname`).
|
|
10
|
+
* No `eval`, no dynamic dispatch of user input, no deserialization and no execution of external content.
|
|
11
|
+
* No network access and no telemetry: the library neither collects nor transmits data.
|
|
12
|
+
* Inputs are the logger options (`level`, `file`, `formatter`, `pattern`, `sync`), the messages and the progname; they affect only local formatting and writing.
|
|
13
|
+
* Paths are used as given: `file:` is opened in append mode and missing parent directories are created. Logaru does not restrict or sandbox paths, so never build a log path from untrusted input.
|
|
14
|
+
|
|
15
|
+
## Considerations
|
|
16
|
+
|
|
17
|
+
### Control characters in messages (not mitigated)
|
|
18
|
+
|
|
19
|
+
Messages are written exactly as received, so control characters (`\e`, `\r`, `\n`, `\b`) can forge log lines or manipulate the terminal of whoever reads the log — for example `"ok\rINFO: all good"`. Neither `Logaru::Formatter` nor `Logaru::Logger` sanitizes messages: sanitize at the source, or return an escaped message from a custom pattern.
|
|
20
|
+
|
|
21
|
+
### Sensitive content
|
|
22
|
+
|
|
23
|
+
Messages may contain personal data, credentials in URLs, tokens or payloads. The library does not mask, redact or filter anything: retention policy, masking and access control are the application's responsibility.
|
|
24
|
+
|
|
25
|
+
### File permissions and lifetime
|
|
26
|
+
|
|
27
|
+
The file is created with the process defaults (`umask`/ACLs apply) in append mode, and missing parent directories are created with default permissions. The handle stays open until `Logger#close`, so release it before archiving or removing the file — on Windows an open file cannot be renamed or deleted.
|
|
28
|
+
|
|
29
|
+
### Writing from several processes
|
|
30
|
+
|
|
31
|
+
Synchronization is per logger and per process. Two processes appending to the same file are not coordinated, so entries may interleave when writes are buffered: keep the default `sync: true`, use one writer per file, or send logs to an external collector.
|
|
32
|
+
|
|
33
|
+
## Reporting a vulnerability
|
|
34
|
+
|
|
35
|
+
* Report privately through GitHub: **Security → Advisories → Report a vulnerability** (<https://github.com/rpzerosixcode/logaru/security/advisories/new>).
|
|
36
|
+
* Do not open a public issue for something exploitable.
|
|
37
|
+
* Include the version or commit, Ruby version, operating system, reproduction steps, impact and, when possible, a suggested fix.
|
|
38
|
+
* There is no bug bounty program. Gem releases require MFA (`rubygems_mfa_required`) and fixes are noted in the changelog.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Logaru
|
|
4
|
+
# Base class for errors raised by Logaru.
|
|
5
|
+
class Error < StandardError; end
|
|
6
|
+
|
|
7
|
+
# Raised when a logger is configured with an invalid formatter.
|
|
8
|
+
class InvalidFormatterError < Error; end
|
|
9
|
+
|
|
10
|
+
# Raised when a logger receives an unsupported severity.
|
|
11
|
+
class InvalidLevelError < Error; end
|
|
12
|
+
|
|
13
|
+
# Raised when a formatter is configured with an unusable pattern.
|
|
14
|
+
class InvalidPatternError < Error; end
|
|
15
|
+
|
|
16
|
+
# Raised when a logger receives an output that cannot be written to.
|
|
17
|
+
class InvalidOutputError < Error; end
|
|
18
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "errors"
|
|
4
|
+
require_relative "level"
|
|
5
|
+
|
|
6
|
+
module Logaru
|
|
7
|
+
# Formats log entries using a configurable pattern.
|
|
8
|
+
class Formatter
|
|
9
|
+
# Number of arguments every pattern must be able to receive.
|
|
10
|
+
PATTERN_ARITY = 4
|
|
11
|
+
|
|
12
|
+
# Pattern used when the formatter is instantiated without one.
|
|
13
|
+
DEFAULT_PATTERN = proc do |severity, datetime, progname, message|
|
|
14
|
+
prefix = progname ? "#{progname} " : ""
|
|
15
|
+
"[#{datetime}] #{prefix}#{Level.name_for(severity).upcase}: #{message}\n"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
attr_reader :pattern
|
|
19
|
+
|
|
20
|
+
# Initializes the formatter with an optional pattern. A block takes
|
|
21
|
+
# precedence over the +pattern+ option, so both
|
|
22
|
+
# Formatter.new(pattern: proc { ... }) and Formatter.new { ... } work.
|
|
23
|
+
def initialize(pattern: DEFAULT_PATTERN, &block)
|
|
24
|
+
@pattern = block || pattern
|
|
25
|
+
validate_pattern!
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Formats a log entry using the configured pattern.
|
|
29
|
+
def format(severity, datetime, progname, message)
|
|
30
|
+
@pattern.call(severity, datetime, progname, message)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def validate_pattern!
|
|
36
|
+
raise InvalidPatternError, "pattern must respond to #call" unless @pattern.respond_to?(:call)
|
|
37
|
+
return if compatible_arity?
|
|
38
|
+
|
|
39
|
+
raise InvalidPatternError, "pattern must accept #{PATTERN_ARITY} arguments"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Returns true when the pattern can be called with the log arguments.
|
|
43
|
+
def compatible_arity?
|
|
44
|
+
return true unless @pattern.respond_to?(:parameters)
|
|
45
|
+
|
|
46
|
+
parameters = @pattern.parameters
|
|
47
|
+
required = parameters.count { |type, _name| type == :req }
|
|
48
|
+
optional = parameters.count { |type, _name| type == :opt }
|
|
49
|
+
variadic = parameters.any? { |type, _name| type == :rest }
|
|
50
|
+
|
|
51
|
+
required <= PATTERN_ARITY && (variadic || required + optional >= PATTERN_ARITY)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
data/lib/logaru/level.rb
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "errors"
|
|
4
|
+
|
|
5
|
+
module Logaru
|
|
6
|
+
# Numeric values for the supported log severities.
|
|
7
|
+
class Level
|
|
8
|
+
DEBUG = 0
|
|
9
|
+
INFO = 1
|
|
10
|
+
WARN = 2
|
|
11
|
+
ERROR = 3
|
|
12
|
+
FATAL = 4
|
|
13
|
+
UNKNOWN = 5
|
|
14
|
+
|
|
15
|
+
# Maps level names to their numeric values.
|
|
16
|
+
LEVELS = {
|
|
17
|
+
"debug" => DEBUG,
|
|
18
|
+
"info" => INFO,
|
|
19
|
+
"warn" => WARN,
|
|
20
|
+
"error" => ERROR,
|
|
21
|
+
"fatal" => FATAL,
|
|
22
|
+
"unknown" => UNKNOWN,
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
25
|
+
class << self
|
|
26
|
+
# Converts a numeric or textual level to its numeric value.
|
|
27
|
+
def coerce(value)
|
|
28
|
+
return value if value.is_a?(Integer) && LEVELS.value?(value)
|
|
29
|
+
|
|
30
|
+
name = value.to_s.downcase
|
|
31
|
+
return LEVELS.fetch(name) if LEVELS.key?(name)
|
|
32
|
+
|
|
33
|
+
raise InvalidLevelError, "unsupported log level: #{value.inspect}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Returns the canonical name for a numeric or textual level.
|
|
37
|
+
def name_for(value)
|
|
38
|
+
LEVELS.key(coerce(value))
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "pathname"
|
|
5
|
+
|
|
6
|
+
require_relative "errors"
|
|
7
|
+
require_relative "level"
|
|
8
|
+
require_relative "formatter"
|
|
9
|
+
|
|
10
|
+
module Logaru
|
|
11
|
+
# Writes log entries through a formatter to the console or a file.
|
|
12
|
+
class Logger
|
|
13
|
+
attr_reader :formatter, :level, :output
|
|
14
|
+
|
|
15
|
+
# Initializes the logger with a level and optional output and formatting.
|
|
16
|
+
#
|
|
17
|
+
# +file+ accepts a path (String or Pathname) or any object responding to
|
|
18
|
+
# #write, such as an open File or a StringIO. When it is omitted, entries
|
|
19
|
+
# are written to $stdout. +sync+ controls whether every write is flushed,
|
|
20
|
+
# which defaults to true so that log files are always up to date.
|
|
21
|
+
#
|
|
22
|
+
# Formatting is handled by a Logaru::Formatter built here. Pass +pattern+
|
|
23
|
+
# (or a block) to configure it, or +formatter+ to inject an instance —
|
|
24
|
+
# never both.
|
|
25
|
+
#
|
|
26
|
+
# Entries are written while holding a mutex, so the same logger can be
|
|
27
|
+
# shared between threads. The formatter pattern is called outside the
|
|
28
|
+
# lock, so it must not depend on mutable shared state.
|
|
29
|
+
def initialize(formatter: nil, level: Level::DEBUG, file: nil, sync: true, pattern: nil, &block)
|
|
30
|
+
validate_formatter_options!(formatter, pattern, block)
|
|
31
|
+
@level = Level.coerce(level)
|
|
32
|
+
@output = file
|
|
33
|
+
@sync = sync
|
|
34
|
+
@formatter = formatter || build_formatter(pattern, &block)
|
|
35
|
+
@resolved_device = nil
|
|
36
|
+
@owned = false
|
|
37
|
+
@mutex = Mutex.new
|
|
38
|
+
validate_formatter!
|
|
39
|
+
validate_output!
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Logs a message using the configured formatter.
|
|
43
|
+
def log(severity, message, progname: nil)
|
|
44
|
+
severity = Level.coerce(severity)
|
|
45
|
+
return if severity < @level
|
|
46
|
+
|
|
47
|
+
formatted = @formatter.format(severity, Time.now, progname, message)
|
|
48
|
+
write(formatted)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Logs a debug message.
|
|
52
|
+
def debug(message, progname: nil)
|
|
53
|
+
log(Level::DEBUG, message, progname:)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Logs an informational message.
|
|
57
|
+
def info(message, progname: nil)
|
|
58
|
+
log(Level::INFO, message, progname:)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Logs a warning message.
|
|
62
|
+
def warn(message, progname: nil)
|
|
63
|
+
log(Level::WARN, message, progname:)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Logs an error message.
|
|
67
|
+
def error(message, progname: nil)
|
|
68
|
+
log(Level::ERROR, message, progname:)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Logs a fatal error message.
|
|
72
|
+
def fatal(message, progname: nil)
|
|
73
|
+
log(Level::FATAL, message, progname:)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Logs a message with an unknown severity.
|
|
77
|
+
def unknown(message, progname: nil)
|
|
78
|
+
log(Level::UNKNOWN, message, progname:)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Returns the IO used for writing, opening the log file on first use.
|
|
82
|
+
def device
|
|
83
|
+
@mutex.synchronize { resolved_device }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Closes the log file opened by the logger. The file is reopened on the
|
|
87
|
+
# next write, which keeps external log rotation working. Streams and
|
|
88
|
+
# objects provided through +file+ are left untouched.
|
|
89
|
+
#
|
|
90
|
+
# Safe to call from another thread: it waits for the write in progress
|
|
91
|
+
# before releasing the handle.
|
|
92
|
+
def close
|
|
93
|
+
@mutex.synchronize do
|
|
94
|
+
@resolved_device.close if @owned && @resolved_device.respond_to?(:close)
|
|
95
|
+
@resolved_device = nil
|
|
96
|
+
@owned = false
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
# Rejects a formatter injected together with a pattern or a block.
|
|
103
|
+
def validate_formatter_options!(formatter, pattern, block)
|
|
104
|
+
return unless formatter && (pattern || block)
|
|
105
|
+
|
|
106
|
+
raise InvalidFormatterError, "pass either formatter or pattern, not both"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Builds the formatter used when none is injected, honoring a pattern.
|
|
110
|
+
def build_formatter(pattern, &)
|
|
111
|
+
Formatter.new(pattern: pattern || Formatter::DEFAULT_PATTERN, &)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def validate_formatter!
|
|
115
|
+
return if @formatter.respond_to?(:format)
|
|
116
|
+
|
|
117
|
+
raise InvalidFormatterError, "formatter must respond to #format (use Logaru::Formatter.new)"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def validate_output!
|
|
121
|
+
return if @output.nil? || path_like?(@output) || @output.respond_to?(:write)
|
|
122
|
+
|
|
123
|
+
raise InvalidOutputError, "output must be a path or an object responding to #write"
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def write(message)
|
|
127
|
+
@mutex.synchronize { resolved_device.write(message) }
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Returns the target of the log entries, opening the log file on first
|
|
131
|
+
# use and reusing it on the following writes. Callers must hold the mutex
|
|
132
|
+
# so the file is opened (and the parent directories created) only once.
|
|
133
|
+
def resolved_device
|
|
134
|
+
return $stdout if @output.nil?
|
|
135
|
+
|
|
136
|
+
@resolved_device ||= open_device
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Returns true when the value stands for a file path instead of a stream.
|
|
140
|
+
#
|
|
141
|
+
# Pathname is handled explicitly because it responds to #write, and that
|
|
142
|
+
# method writes the whole file in one call: treating a Pathname as a
|
|
143
|
+
# stream would make every entry overwrite the log file.
|
|
144
|
+
def path_like?(value)
|
|
145
|
+
value.is_a?(String) || value.is_a?(Pathname) || (!value.respond_to?(:write) && value.respond_to?(:to_path))
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Opens the log file for path targets and returns caller-owned streams as
|
|
149
|
+
# they are.
|
|
150
|
+
def open_device
|
|
151
|
+
return @output unless path_like?(@output)
|
|
152
|
+
|
|
153
|
+
@owned = true
|
|
154
|
+
open_file(@output)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Opens (creating, when needed, the parent directories) the log file in
|
|
158
|
+
# append mode. The handle is kept open and released by #close, so entries
|
|
159
|
+
# are appended to the same file between writes.
|
|
160
|
+
def open_file(path)
|
|
161
|
+
path = path.to_path if path.respond_to?(:to_path)
|
|
162
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
163
|
+
# The handle is intentionally kept open until #close, so the block
|
|
164
|
+
# form of File.open would defeat the reuse between writes.
|
|
165
|
+
file = File.open(path, "a") # rubocop:disable Style/FileOpen
|
|
166
|
+
file.sync = @sync
|
|
167
|
+
file
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
data/lib/logaru.rb
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "logaru/version"
|
|
4
|
+
require_relative "logaru/errors"
|
|
5
|
+
require_relative "logaru/level"
|
|
6
|
+
require_relative "logaru/formatter"
|
|
7
|
+
require_relative "logaru/logger"
|
|
8
|
+
|
|
9
|
+
# Entry point of the Logaru library.
|
|
10
|
+
module Logaru
|
|
11
|
+
class << self
|
|
12
|
+
# Returns the absolute path to the gem root directory.
|
|
13
|
+
def root
|
|
14
|
+
File.expand_path("..", __dir__)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: logaru
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- rpzerosixcode
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-09-25 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rake
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '13.4'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '13.4'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rspec
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '3.13'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '3.13'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rubocop
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '1.90'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '1.90'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rubocop-rake
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0.7'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0.7'
|
|
69
|
+
description: Ruby library focused on clear, organized, and configurable log output
|
|
70
|
+
for applications.
|
|
71
|
+
email:
|
|
72
|
+
executables: []
|
|
73
|
+
extensions: []
|
|
74
|
+
extra_rdoc_files: []
|
|
75
|
+
files:
|
|
76
|
+
- CHANGELOG.md
|
|
77
|
+
- LICENSE
|
|
78
|
+
- README.md
|
|
79
|
+
- Rakefile
|
|
80
|
+
- docs/ARCHITECTURE.md
|
|
81
|
+
- docs/FEATURES.md
|
|
82
|
+
- docs/SECURITY.md
|
|
83
|
+
- lib/logaru.rb
|
|
84
|
+
- lib/logaru/errors.rb
|
|
85
|
+
- lib/logaru/formatter.rb
|
|
86
|
+
- lib/logaru/level.rb
|
|
87
|
+
- lib/logaru/logger.rb
|
|
88
|
+
- lib/logaru/version.rb
|
|
89
|
+
homepage: https://github.com/rpzerosixcode/logaru
|
|
90
|
+
licenses:
|
|
91
|
+
- MIT
|
|
92
|
+
metadata:
|
|
93
|
+
homepage_uri: https://github.com/rpzerosixcode/logaru
|
|
94
|
+
source_code_uri: https://github.com/rpzerosixcode/logaru/tree/main
|
|
95
|
+
documentation_uri: https://github.com/rpzerosixcode/logaru#readme
|
|
96
|
+
changelog_uri: https://github.com/rpzerosixcode/logaru/blob/main/CHANGELOG.md
|
|
97
|
+
keywords: ruby, logger, logging, logs
|
|
98
|
+
rubygems_mfa_required: 'true'
|
|
99
|
+
post_install_message:
|
|
100
|
+
rdoc_options: []
|
|
101
|
+
require_paths:
|
|
102
|
+
- lib
|
|
103
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
104
|
+
requirements:
|
|
105
|
+
- - ">="
|
|
106
|
+
- !ruby/object:Gem::Version
|
|
107
|
+
version: 3.3.0
|
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
109
|
+
requirements:
|
|
110
|
+
- - ">="
|
|
111
|
+
- !ruby/object:Gem::Version
|
|
112
|
+
version: '0'
|
|
113
|
+
requirements: []
|
|
114
|
+
rubygems_version: 3.5.22
|
|
115
|
+
signing_key:
|
|
116
|
+
specification_version: 4
|
|
117
|
+
summary: A Ruby library for clear, organized, and configurable log output.
|
|
118
|
+
test_files: []
|