cute_logger 0.1.5 → 0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +113 -8
- data/cute_logger.gemspec +6 -2
- data/docs/better_logging.md +275 -0
- data/lib/cute_logger.rb +1 -1
- data/lib/cute_logger/version.rb +1 -1
- metadata +23 -5
- data/lib/utf8_enforcer.rb +0 -27
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 35f4723eac7384b0d7e3c4361883830d178cb6f6
|
4
|
+
data.tar.gz: f43c61f6d84d4efdb128b5bab4c5df6c93a193d3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 49ff3b09c923bc05d50e8d039e882b8905b242bc66d77b4d6f4678eeafa3b38918def050a53ae76e120429dc303a5444b9b6f75f290b5d6e3bf2710bc8b1b52e
|
7
|
+
data.tar.gz: ee3e0e8d6a4254d958055a7ed4aea6b6f7e96f2fce47787490a80bd3d2df67dfb151573ce058122c9e1c1748ff5c05b7aff68b0b112e66e9c56964543856cc8b
|
data/README.md
CHANGED
@@ -1,6 +1,14 @@
|
|
1
1
|
# Cute Logger
|
2
2
|
|
3
|
-
This gem provides methods
|
3
|
+
This gem provides accesible methods for doing the application logging in a simple manner.
|
4
|
+
|
5
|
+
## Description
|
6
|
+
|
7
|
+
Cute Logger provides globally accesible methods to do the logging. It also provides a log parser
|
8
|
+
command for easy view during development. The gem includes mechanisms for log rotation, improved
|
9
|
+
exception logging and nice formatted log viewing among many other features and best practices.
|
10
|
+
|
11
|
+
Please refer to the document [Better Logging](docs/better_logging.md) for a better understanding of the functionality of Cute Logger.
|
4
12
|
|
5
13
|
## Installation
|
6
14
|
|
@@ -10,15 +18,25 @@ Add this line to your application's Gemfile:
|
|
10
18
|
gem 'cute_logger'
|
11
19
|
```
|
12
20
|
|
21
|
+
And then execute:
|
22
|
+
|
23
|
+
```bash
|
24
|
+
$ bundle update
|
25
|
+
```
|
26
|
+
|
13
27
|
Or install it yourself as:
|
14
28
|
|
15
|
-
|
29
|
+
```bash
|
30
|
+
$ gem install cute_logger
|
31
|
+
```
|
16
32
|
|
17
33
|
## Usage
|
18
34
|
|
19
|
-
To use this gem, require it into your application and optionally call the setup function to specify how the information should be stored.
|
35
|
+
To use this gem, require it into your application and **optionally** call the setup function to specify how the log information should be stored, for how long and the minimum accepted severity.
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
require 'cute_logger'
|
20
39
|
|
21
|
-
```
|
22
40
|
CuteLogger.setup(
|
23
41
|
filename: 'application.log',
|
24
42
|
severity: 'INFO',
|
@@ -27,24 +45,111 @@ CuteLogger.setup(
|
|
27
45
|
)
|
28
46
|
```
|
29
47
|
|
48
|
+
The previous values are actually the **default** ones if the `setup` method is not explicitly called.
|
49
|
+
|
30
50
|
To log something you could use the following formats:
|
31
51
|
|
32
|
-
```
|
33
|
-
log_info('Some
|
34
|
-
log_info(status: 'Working', value: '123')
|
52
|
+
```ruby
|
53
|
+
log_info('Some event')
|
54
|
+
log_info('Some event', status: 'Working', value: '123')
|
35
55
|
log_debug(my_hash)
|
36
56
|
log_debug { 'Delayed evaluation' }
|
37
57
|
log_info('MyAppName') { 'Something to log' }
|
38
58
|
log_error('Error X', my_exception)
|
39
59
|
```
|
40
60
|
|
41
|
-
|
61
|
+
Hashes and arrays are logged as JSON format:
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
my_array = {a: 'letter A', b: [1, 2, 3]}
|
65
|
+
log_info('Useful data', data: my_array)
|
66
|
+
```
|
67
|
+
|
68
|
+
Results in an entry like the following:
|
69
|
+
|
70
|
+
```bash
|
71
|
+
2017-04-21 22:12:56 -0500,INFO,a801,3ff44803f9f4,Object,["Useful data",{"data":{"a":"letter A","b":["1","2","3"]}}]
|
72
|
+
```
|
73
|
+
|
74
|
+
To view the log in a cute format (awesome_print):
|
75
|
+
|
76
|
+
```bash
|
77
|
+
$ cat application.log | cute_log
|
78
|
+
```
|
79
|
+
|
80
|
+
```bash
|
81
|
+
2017-04-21 22:12:56 -0500 INFO 43009-3ff44803f9f4 (Object)
|
82
|
+
[
|
83
|
+
[0] "Useful data",
|
84
|
+
[1] {
|
85
|
+
"data" => {
|
86
|
+
"a" => "letter A",
|
87
|
+
"b" => [
|
88
|
+
[0] "1",
|
89
|
+
[1] "2",
|
90
|
+
[2] "3"
|
91
|
+
]
|
92
|
+
}
|
93
|
+
}
|
94
|
+
]
|
95
|
+
```
|
96
|
+
|
97
|
+
Exceptions have their own formatting:
|
42
98
|
|
99
|
+
```ruby
|
100
|
+
begin
|
101
|
+
nil.hello
|
102
|
+
rescue => error
|
103
|
+
log_error('Error during X event', error: error)
|
104
|
+
end
|
43
105
|
```
|
106
|
+
|
107
|
+
```bash
|
44
108
|
$ cat application.log | cute_log
|
109
|
+
```
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
2017-04-21 22:18:28 -0500 ERROR 44691-3fe53703fa14 (Object)
|
113
|
+
[
|
114
|
+
[0] "Error during X event",
|
115
|
+
[1] {
|
116
|
+
"error" => {
|
117
|
+
"class" => "NoMethodError",
|
118
|
+
"message" => "undefined method `hello' for nil:NilClass",
|
119
|
+
"backtrace" => [
|
120
|
+
[ 0] "(irb):3:in `irb_binding'",
|
121
|
+
[ 1] "/Users/johndoe/.rbenv/versions/2.3.1/lib/ruby/2.3.0/irb/workspace.rb:87:in `eval'",
|
122
|
+
[ 2] "/Users/johndoe/.rbenv/versions/2.3.1/lib/ruby/2.3.0/irb/workspace.rb:87:in `evaluate'",
|
123
|
+
[ 3] "/Users/johndoe/.rbenv/versions/2.3.1/lib/ruby/2.3.0/irb/context.rb:380:in `evaluate'",
|
124
|
+
[ 4] "/Users/johndoe/.rbenv/versions/2.3.1/lib/ruby/2.3.0/irb.rb:489:in `block (2 levels) in eval_input'",
|
125
|
+
... log intentionally cutted down for better legibility ...
|
126
|
+
]
|
127
|
+
}
|
128
|
+
}
|
129
|
+
]
|
130
|
+
```
|
131
|
+
|
132
|
+
|
133
|
+
The `cute_log` script utility helps to visualize the contents of the log. For example:
|
134
|
+
|
135
|
+
```
|
136
|
+
# View the all the logs
|
137
|
+
$ cat application.log | cute_log
|
138
|
+
|
139
|
+
# View the logs in real time with awesome print
|
45
140
|
$ tail -F application.log | cute_log
|
141
|
+
|
142
|
+
# View the logs in real time with JSON pretty_generate
|
46
143
|
$ tail -F application.log | cute_log --json
|
144
|
+
|
145
|
+
# View all errors with awesome print (The switch --awesome is the default anyway)
|
47
146
|
$ grep "ERROR" application.log | cute_log --awesome
|
147
|
+
|
148
|
+
# View all errors of ID 223
|
149
|
+
$ grep "ERROR" application.log | grep "ID000233" | cute_log
|
150
|
+
|
151
|
+
# View all warns from Jaunuary 2017
|
152
|
+
$ grep "WARN" application.log | grep "2017-01" | cute_log
|
48
153
|
```
|
49
154
|
|
50
155
|
## Development
|
data/cute_logger.gemspec
CHANGED
@@ -9,8 +9,11 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.authors = ['Jorge Del Rio']
|
10
10
|
spec.email = ['jdelrios@gmail.com']
|
11
11
|
|
12
|
-
spec.summary = '
|
13
|
-
spec.description = '
|
12
|
+
spec.summary = 'This gem provides accesible methods for doing the application logging in a simple manner'
|
13
|
+
spec.description = 'Cute Logger provides globally accesible methods to do the logging. It also provides a' +
|
14
|
+
' log parser command for easy view during development. The gem includes mechanisms for' +
|
15
|
+
' log rotation, improved exception logging and nice formatted log viewing among many' +
|
16
|
+
' other features and best practices.'
|
14
17
|
spec.homepage = 'https://github.com/newint33h/cute_logger'
|
15
18
|
spec.license = 'MIT'
|
16
19
|
|
@@ -20,6 +23,7 @@ Gem::Specification.new do |spec|
|
|
20
23
|
spec.require_paths = ['lib']
|
21
24
|
|
22
25
|
spec.add_runtime_dependency 'awesome_print', '~> 1'
|
26
|
+
spec.add_runtime_dependency 'utf8_converter', '~> 0.1'
|
23
27
|
|
24
28
|
spec.add_development_dependency 'bundler', '~> 1.10'
|
25
29
|
spec.add_development_dependency 'rake', '~> 10.0'
|
@@ -0,0 +1,275 @@
|
|
1
|
+
# Better Logging
|
2
|
+
One of the main purposes of logging is to be able to debug an application in case of a system failure. After thinking for a while about how we should log all the information to make it effective for debugging, I ended up with a bunch of rules we had to implement in the new gem to have quality logs. Some of such rules are:
|
3
|
+
|
4
|
+
* To be integrable with third party log viewers,
|
5
|
+
* To be multithreaded,
|
6
|
+
* To be Searchable searchable and
|
7
|
+
* To have meaningful output.
|
8
|
+
|
9
|
+
The main goal is to debug an application in case of error. However, there are many other cases to consider when logging, like how the application can be audited to determine if someone is hacking the system, or responding questions of how many times certain event happen with specific conditions. Or simply determine what changes were made by some employee. In any case, writing the logs in the right format can help you in the future to answer all these questions and more.
|
10
|
+
|
11
|
+
## Rules for a good logging system
|
12
|
+
Let's look at a couple of good practices that can help us create a good logging system. These are a set of rules that we implemented in our logging gem.
|
13
|
+
|
14
|
+
### One line logs
|
15
|
+
Logs that are one line long are perfect for being searched using console commands like `grep` without having to get the lines after and before to understand the log meaning. It is also easier for a log parser to determine where the log starts and ends. The line length can be any size, but one line.
|
16
|
+
|
17
|
+
```
|
18
|
+
# The next log is in one line, no matter the if log data contains multiple lines. Lines are "squashed" into a single log line
|
19
|
+
2016-09-11 14:04:51 -0500,ERROR,17083,3ffbceb4f99c,MyApp,["Something happen",{"result":"Some large text\nmultiline"}]
|
20
|
+
```
|
21
|
+
|
22
|
+
Even if the log content has new line elements by the nature of the data, these are escaped to encapsulate the full log entry in one line.
|
23
|
+
|
24
|
+
### Encoded User Input
|
25
|
+
All data that comes from the user must be encoded to avoid messing with special characters. A good format option for encoding the user input is using JSON, because this format does not use RETURNs and escapes special characters. It is important to ensure that the logging function encodes the logs in one charset. UTF-8 is very good choice for general purpose logs, however you should choose the encoding that best fits your needs.
|
26
|
+
|
27
|
+
```
|
28
|
+
# The next log contains carriage return characters encoded in JSON
|
29
|
+
2016-09-11 14:04:51 -0500,ERROR,17083,3ffbceb4f99c,MyApp,["Something happen",{"result":"Some large text\nmultiline\nline 3"}]
|
30
|
+
```
|
31
|
+
|
32
|
+
### Time and Timezone
|
33
|
+
Always log the time in UTC or ensure that your logs contain the correct timezone, and leave the job of displaying the time in local timezone to the tool in charge of visualizing the logs. Having milliseconds in the log's time is very useful to debug logs generated in the same server, unless you have all your servers in sync. As a suggestion, synchronize your servers with a NTP daemon.
|
34
|
+
|
35
|
+
```
|
36
|
+
# The next log contains the time encoded in UNIX timestamp format
|
37
|
+
1473620691361,ERROR,17083,3ffbceb4f99c,MyApp,["Something happen",{"result":"Some large text\nmultiline"}]
|
38
|
+
```
|
39
|
+
|
40
|
+
### Boilerplate log fields
|
41
|
+
There are some fields that always should be present in any log, like the time, severity, process ID, thread ID, and application identifier. The time and severity fields are obvious, but the process ID and thread ID are not. The process ID can identify the logs of an specific application if you are centralizing logs of many servers. Since the process ID values are limited to a short range and can collide among different servers, it is sometimes useful to add an unique machine identifier to the log. The thread ID is useful in any multi-threaded application to identify logs of a thread. In web development this is more important because with this field you can filter the logs corresponding to a single request. And finally the application identifier field is useful for filtering logs in a centralized log database.
|
42
|
+
|
43
|
+
All this fields should be abstracted in a log function that the application uses as the default log method. With this approach, the task of adding logs with all this information will be very simple for users o the logging gem.
|
44
|
+
|
45
|
+
```
|
46
|
+
# Time, Severity, Process ID, Thread ID, Application Tag, Message in JSON
|
47
|
+
2016-09-29 16:04:51 -0500,ERROR,17083,3ffbceb4f99c,Class,["Something happen",{"result":"Some large text\nmultiline"}]
|
48
|
+
```
|
49
|
+
|
50
|
+
|
51
|
+
## Rules for effective logging
|
52
|
+
Having a gem that implements the mentioned features is not even half of the way to have quality logs. As a developer, we need to follow some rules about how to write logs properly.
|
53
|
+
|
54
|
+
### Logging enough information
|
55
|
+
A good log must have all the variables needed to read the code and understand what path the execution took. That way, the programmer can reproduce the execution path by reading the log.
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
def do_something(id, index, value_x)
|
59
|
+
random_value = rand(100)
|
60
|
+
…
|
61
|
+
# Logging all the variables to ensure an easy analysis in the future
|
62
|
+
log_debug('Something happen', id: id, index: index, value_x: value_x, random_value: random_value)
|
63
|
+
...
|
64
|
+
end
|
65
|
+
```
|
66
|
+
|
67
|
+
### Multilevel error logs
|
68
|
+
A good code is divided in different layers that have a specific function. Logging the same log in different layers is a good practice as long as each layer is tagged properly and the information is not duplicated. For example, a service that consults via web service the actual exchange rate of dollars to another currency may have a connection timeout error, and that should log the request exception with the parameters needed to reproduce the error. But it's also possible to create another log entry in an upper layer to indicate that the operation to check the current exchange rate failed. Moreover, it is possible to generate a third log entry in the uppermost layer indicating that the action requested by the user failed. Each layer knows the context in which the execution is taking place, and that knowledge should be reflected in logs.
|
69
|
+
|
70
|
+
For the sake of simplicity, I wrote pseudocode with mixed Javascript and Ruby:
|
71
|
+
|
72
|
+
```javascript
|
73
|
+
function on_click(button) {
|
74
|
+
value_a = $("value1Input").val();
|
75
|
+
value_b = $("value2Input").val();
|
76
|
+
x = business_rule(value_a, value_b); # Function to get the result from the backend
|
77
|
+
if (x.error) {
|
78
|
+
# The only error handling to do here is to notify the user about the problem
|
79
|
+
alert(x.error);
|
80
|
+
} else {
|
81
|
+
$("result").val(x.result);
|
82
|
+
}
|
83
|
+
}
|
84
|
+
```
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
def business_rule(value_a, value_ b)
|
88
|
+
try
|
89
|
+
a = query_service(value_a)
|
90
|
+
return a / value_b
|
91
|
+
catch DivisionByZeroException
|
92
|
+
# No log required because we know how to handle it
|
93
|
+
return a # Business rule dictates that when value_b is zero, the default is 1.
|
94
|
+
catch NotFoundException
|
95
|
+
return 0 # Business rule dictates that if value_a is not found in the service, return 0
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def query_service(value)
|
100
|
+
try
|
101
|
+
result = HTTP.request(url, data: {value: value}) # The service returns 404 if the value is not found and that raises an NotFoundException
|
102
|
+
return result.body
|
103
|
+
catch TimeoutException
|
104
|
+
# We need to log, probably if we see many warnings we can decide to switch to another provider
|
105
|
+
log_warn('Connection timeout with service X', value: value)
|
106
|
+
redo
|
107
|
+
end
|
108
|
+
end
|
109
|
+
```
|
110
|
+
|
111
|
+
### Protect confidential information
|
112
|
+
Confidential information must NOT be logged. Instead of logging sensitive information, we must log IDs that a person with authority can relate with the real data. The log registry must be confidential and should be used only by a limited number of people. Avoiding logging confidential information is an always welcome protection to guarantee the privacy of clients and reduce the risk of a data breach.
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
# Wrong
|
116
|
+
log_debug('User balance changed', name: data[:name], account_balance: data[:account_balance])
|
117
|
+
|
118
|
+
# Right
|
119
|
+
log_debug('User balance changed', user_id: data[:user_id], account_balance: data[:account_balance])
|
120
|
+
```
|
121
|
+
|
122
|
+
### Logs everywhere
|
123
|
+
All paths of the code must have a log entry, enough to tell a story of the execution of the application. Very general paths must be logged using INFO severity, and very deep and detailed paths must be logged using DEBUG severity. If a code has a know issue that may affect the users, a log with WARN severity is a good way to keep that issue in the aim.
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
def do_something_important(x, y)
|
127
|
+
# A log to indicate that an important action has started
|
128
|
+
log_info('Some important', x: x, y: y)
|
129
|
+
|
130
|
+
if x + y < 0
|
131
|
+
# Warn about this special case that may cause problems
|
132
|
+
log_warn('Special case of something', sum: x + y)
|
133
|
+
end
|
134
|
+
|
135
|
+
total = (x + y) * CONSTANT_Z + random(5) # A complex formula
|
136
|
+
# Log detailed information about this complex formula for debugging
|
137
|
+
log_debug("Result of complex formula of something", total: total)
|
138
|
+
|
139
|
+
rescue => error
|
140
|
+
# If something unexpected happend, log it
|
141
|
+
log_error('Error executing something', error: error)
|
142
|
+
end
|
143
|
+
```
|
144
|
+
|
145
|
+
### Global Exception Handler
|
146
|
+
A global exception handler must be defined to log all exceptions, and this is a log with FATAL severity. This ensures that any problem not foreseen can be logged.
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
# This will catch all the exceptions and generate a fatal severity log.
|
150
|
+
def global_exeception_handler(error)
|
151
|
+
log_fatal("Fatal error", error: error)
|
152
|
+
end
|
153
|
+
```
|
154
|
+
|
155
|
+
### Countable logs
|
156
|
+
A good log is something that can be counted. For example, the message that logs the user sign in should only contain "User signin" without any other information in the message. This allows a simple parser to split the log using commas and look for the exact message without worrying about special characters submitted by the user and affecting the parsing. This way, it is possible to generate metrics counting logs. Any additional information that needs to be logged can be added in an additional log fields.
|
157
|
+
|
158
|
+
```ruby
|
159
|
+
# Wrong
|
160
|
+
log_info("User login: #{user_id}")
|
161
|
+
|
162
|
+
# Right
|
163
|
+
log_info('User login', user_id: user_id)
|
164
|
+
```
|
165
|
+
|
166
|
+
### Additional log fields
|
167
|
+
Almost all logs have a context of execution in which there is at least one object ID that is the protagonist of all the action. That should be logged as an additional field in the log and NOT as part of the message.
|
168
|
+
There may be additional fields that can be considered essential and must always be logged. For example, if an application can only work with a signed user, the log function must always log the signed user by default. Sometimes the application may have very well defined responsibilities and adding a tag may be very useful. For example, an application may want to differentiate among network operations and business rules. The tag NETWORK may be used for logs related to the code that establish the connection with a service, and the tag BUSINESS may be used to log information about the logic in the application.
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
# Using tags to group logs and simplify future searches
|
172
|
+
log_warn('Network failure', tag: 'NETWORK', ...)
|
173
|
+
log_warn('Calculating loan interest', tag: 'BUSINESS' loan: loan_id, interest: loan_interest)
|
174
|
+
```
|
175
|
+
|
176
|
+
### Log chaining
|
177
|
+
Design logs to be chainable, in other words, that one log can lead you to identify a bunch of other logs. For example, If you want to know what happened with the request of the person X, first search for the log message 'User signin' of user 'X'. That log must have a thread ID that should allow you to search for all the actions that happened during the request. This way, it is not needed to include the user's name in all logs.
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
log_info('User login', user_id: user_id, name: user_name)
|
181
|
+
```
|
182
|
+
|
183
|
+
```bash
|
184
|
+
$ grep "John Doe" application.log
|
185
|
+
|
186
|
+
2016-09-11 14:04:51 -0500,INFO,17083,3ffbceb4f99c,MyApp,["User login",{"user_id":"2123","name":"John Doe"}]
|
187
|
+
|
188
|
+
$ grep "3ffbceb4f99c" application.log
|
189
|
+
|
190
|
+
2016-09-11 14:04:51 -0500,INFO,17083,3ffbceb4f99c,MyApp,["Load permissions",{"permissions":"RW"}]
|
191
|
+
2016-09-11 14:04:51 -0500,INFO,17083,3ffbceb4f99c,MyApp,["Load Preferences",{"preferences":{"font-size":"34","background-color":"green"}}]
|
192
|
+
```
|
193
|
+
|
194
|
+
### Rethrow vs throw new exceptions
|
195
|
+
There is a very thin line in between when to throw a new exception and when rethrow the last exception. Rethrow preserves the original message and line code of where the error raised while a new exception may contain different information depending of the context. Both possibilities are valid only when the actual code does not know how to handle the error properly and we need to transfer the responsibility to another part of the application.
|
196
|
+
|
197
|
+
Throwing new exception is useful when actual layer has additional information that may be useful for future purposes. Also is a good practice to throw new exception on each application layer, so a person interested in the business logic can filter for the error messages in that layer and have a meaningful error log.
|
198
|
+
|
199
|
+
Rethrowing the same exception is useful when you don't know how to handle the exception. For example, a code that query a service may handle a connection timeout exception by trying again, but a wrong credentials exception will require to rethrow the exception hoping that an upper layer knows how to handle it. When throwing a new exception, logging the old exception is a must, and when rethrowing the last exception the log is not needed.
|
200
|
+
|
201
|
+
Another approach is to always throw new exceptions nesting the previous exception in a new one. This allows to see how the exception bubbled up preserving the original error message and how this exception affects the different layers.
|
202
|
+
|
203
|
+
```ruby
|
204
|
+
# If is important, log the parameters in the error
|
205
|
+
def a_method(x, y)
|
206
|
+
try
|
207
|
+
do_something_dangerous()
|
208
|
+
catch NiceAndExpectedError => error
|
209
|
+
# no log required, this was expected
|
210
|
+
return DEFAULT_VALUE
|
211
|
+
catch CustomError => error
|
212
|
+
# An error we know how to handle
|
213
|
+
log_warn('Error happen', x: x, y: y, error: error)
|
214
|
+
return workaround_method(x, y)
|
215
|
+
catch UnexpectedError => error
|
216
|
+
# We don't know how to handle this, so notify the user and do nothing
|
217
|
+
log_error('Error happen', x: x, y: y, error: error)
|
218
|
+
notify_user(error) # This is not the right place to do this, but you get the idea
|
219
|
+
abort() # Kill the thread/request, or simply ensure to do nothing after finishing this method
|
220
|
+
catch SpecialCaseError => error
|
221
|
+
# Log parameters for this special case and throw a new exception more manageable to the upper layer
|
222
|
+
log_warn('Special case triggered', x: x, y: y, error: error)
|
223
|
+
raise AnotherError.new('Error doing something')
|
224
|
+
catch StandardError => error
|
225
|
+
# Rethrowing the error, someone else's problem. (Is the same as not having this catch)
|
226
|
+
raise error
|
227
|
+
end
|
228
|
+
end
|
229
|
+
```
|
230
|
+
|
231
|
+
### Error resolution
|
232
|
+
Many errors can't be resolved by the application itself and require special attention. In these cases, the only solution is to inform the user about the problem and log the error with the proper severity. Some other errors are solved as time passes and some when the services recover itself, and in that case, the solution is to inform the user to try again later. In those examples, the error resolution is handled in the UI layer, and there is the place to catch the exception and log the error.
|
233
|
+
|
234
|
+
```ruby
|
235
|
+
get '/users' do
|
236
|
+
try
|
237
|
+
users = Database.get_users()
|
238
|
+
catch CustomError => error
|
239
|
+
log_error('Failed to retrieve users', error: error)
|
240
|
+
json_response({"status": "error", "message": error.message}) # Let the front end handle this
|
241
|
+
end
|
242
|
+
end
|
243
|
+
```
|
244
|
+
|
245
|
+
### Common ways to group and read logs
|
246
|
+
It is important to keep in mind the usual ways of reading logs, so the programmers can write their logs thinking in how effective they will be.
|
247
|
+
|
248
|
+
**Chronological** - Reading the logs sorted by time is the most common way of understanding what is happening, however this may be very hard to read if the logs are mixed with different threads executing the same actions.
|
249
|
+
|
250
|
+
**Severity** - This is useful if you want to view the errors in the system to pick one and solve it. But only useful for FATAL and ERROR severity.
|
251
|
+
|
252
|
+
**Thread ID** - This is very useful to read an history of one execution flow of an application.
|
253
|
+
|
254
|
+
**Tag** - Useful to only show logs related to an specific area or application layer.
|
255
|
+
|
256
|
+
**User** - Very useful to trace what was the user doing. This helps a lot to detect corner cases that the user triggers due their uncommon behaviour with the application.
|
257
|
+
|
258
|
+
**ID** - Using custom IDs to identify a resource can help you filter all the logs that has something to do with the given ID.
|
259
|
+
|
260
|
+
### Log severities
|
261
|
+
Identify the proper severity for the logs is essential to have an effective problem identification.
|
262
|
+
|
263
|
+
**FATAL** is when the immediate intervention is needed and the system can't continue running. (This should raise the alarms somewhere in your office).
|
264
|
+
**ERROR** is when something needs a fix because the system is failing and the information may be losing.
|
265
|
+
**WARN** is when there is potentially an error that may affect some the system. Also a warning could be issued when there is technical debt or something that is not fully implemented.
|
266
|
+
**INFO** is when you need to inform about an important event that is useful to determinate the lifecycle of the execution.
|
267
|
+
**DEBUG** is basically anything the programmer want to log to have better visibility of the values of the application in some part of the code.
|
268
|
+
|
269
|
+
## Summary
|
270
|
+
A lot can be said of how to treat logging in an application, but the idea here was to shed some light on tips and techniques that I have found to be useful to cope with the ever growing amount of information that is being produced by nowadays production systems.
|
271
|
+
|
272
|
+
Logging is a difficult task. We feel there is no correct or incorrect approach to logging, and how to implement it correctly depends on the needs of your system. Nevertheless we hope that this brief article provides some useful ideas.
|
273
|
+
|
274
|
+
This gem was inspired by a private gem developed at kueski.com, aswell this article which was slightly modified to be released in the public domain. Please refer to the original article at:
|
275
|
+
[http://nerds.kueski.com/better-logging/](http://nerds.kueski.com/better-logging/)
|
data/lib/cute_logger.rb
CHANGED
data/lib/cute_logger/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cute_logger
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jorge Del Rio
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-04-
|
11
|
+
date: 2017-04-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: awesome_print
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: utf8_converter
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.1'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: bundler
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,7 +80,10 @@ dependencies:
|
|
66
80
|
- - "~>"
|
67
81
|
- !ruby/object:Gem::Version
|
68
82
|
version: '5'
|
69
|
-
description:
|
83
|
+
description: Cute Logger provides globally accesible methods to do the logging. It
|
84
|
+
also provides a log parser command for easy view during development. The gem includes
|
85
|
+
mechanisms for log rotation, improved exception logging and nice formatted log viewing
|
86
|
+
among many other features and best practices.
|
70
87
|
email:
|
71
88
|
- jdelrios@gmail.com
|
72
89
|
executables:
|
@@ -80,9 +97,9 @@ files:
|
|
80
97
|
- Rakefile
|
81
98
|
- bin/cute_log
|
82
99
|
- cute_logger.gemspec
|
100
|
+
- docs/better_logging.md
|
83
101
|
- lib/cute_logger.rb
|
84
102
|
- lib/cute_logger/version.rb
|
85
|
-
- lib/utf8_enforcer.rb
|
86
103
|
homepage: https://github.com/newint33h/cute_logger
|
87
104
|
licenses:
|
88
105
|
- MIT
|
@@ -106,5 +123,6 @@ rubyforge_project:
|
|
106
123
|
rubygems_version: 2.5.1
|
107
124
|
signing_key:
|
108
125
|
specification_version: 4
|
109
|
-
summary:
|
126
|
+
summary: This gem provides accesible methods for doing the application logging in
|
127
|
+
a simple manner
|
110
128
|
test_files: []
|
data/lib/utf8_enforcer.rb
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
# String extensions to convert any encoding to utf8
|
2
|
-
class String
|
3
|
-
def try_convert_from_encoding_to_utf8!(encoding)
|
4
|
-
original_encoding = self.encoding
|
5
|
-
begin
|
6
|
-
force_encoding(encoding).encode!(Encoding::UTF_8)
|
7
|
-
true
|
8
|
-
rescue
|
9
|
-
force_encoding(original_encoding)
|
10
|
-
false
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
def to_utf8!
|
15
|
-
if force_encoding(Encoding::UTF_8).valid_encoding?
|
16
|
-
return encode!(Encoding::UTF_8, invalid: :replace, replace: '?')
|
17
|
-
end
|
18
|
-
return self if try_convert_from_encoding_to_utf8!(Encoding::ISO_8859_1)
|
19
|
-
return self if try_convert_from_encoding_to_utf8!(Encoding::Windows_1252)
|
20
|
-
return self if try_convert_from_encoding_to_utf8!(Encoding::Windows_1252)
|
21
|
-
encode!(Encoding::UTF_8, invalid: :replace, replace: '?')
|
22
|
-
end
|
23
|
-
|
24
|
-
def to_utf8
|
25
|
-
dup.to_utf8!
|
26
|
-
end
|
27
|
-
end
|